Today’s goal is discuss the method of implementing bidirectional dictionary in Python.
Environment
Windows 10 Python 3.8.7
What is bidirectional dictionary?
Dictionary or hash table is a data structure composed of a collection of (key, map) pair where keys are unique, which is known as an associative array, map, symbol table, or dictionary. The advantage of dictionary is its small time complexity O(1).
Bidirectional dictionary I’d like is the dictionary that return the value when the key is given and return keys when the value is given.
# what I'd like to do
job_bidict = {"John":"director", "Mike":"designer", "Anna":"designer", "Lisa":"engineer"}
# job_bidict["Mike"] returns "designer"
# job_bidict["designer"] returns "Mike", "Anna" or ["Mike", "Anna"]
from bidict import bidict
job_bidict = bidict({"John":"director", "Mike":"designer", "Lisa":"engineer"}) # The values should be unique
print(job_bidict["Mike"])
# output => designer
print(job_bidict.inverse["designer"])
# output => Mike
Advantage
It’s easy to implement.
The library has many useful functions
Disadvantage
The values of bidict should be unique.
The file size grows when the program is distributed.
Advantage: It’s easy to implement. Disadvantage: The file size grows when you distribute the program.
Method 2. Use 2 dicts in a class
class MyBidict:
def __init__(self, init_dict):
self.dict_normal = {} # normal dict to store (key, values)
self.dict_inverse = {} # normal dict to store (value. keys)
for key in init_dict.keys():
self.add_item(key, init_dict[key])
def add_item(self, key, value):
if key in self.dict_normal:
self.dict_normal[key].append(value)
else:
self.dict_normal[key] = [value]
if value in self.dict_inverse:
self.dict_inverse[value].append(key)
else:
self.dict_inverse[value] = [key]
def get_item(self, key):
return self.dict_normal[key]
def get_item_inverse(self, value):
return self.dict_inverse[value]
job_bidict = MyBidict({"John":"director", "Mike":"designer", "Anna":"designer", "Lisa":"engineer"})
print(job_bidict.get_item("Mike"))
# output => ['designer']
print(job_bidict.get_item_inverse("designer"))
# output => ['Mike', 'Anna']
Advantage
You can customize the specification of the dict such as whether it allows duplicate values or not.
You can add functions you need.
Disadvantage
It takes time and effort to implement.
The specification is sometimes different from the built-in dict.
Method 3. Implement Bidict class as a subclass of dict
class MyBidict(dict):
def __init__(self, init_dict):
self.inverse = {}
dict.__init__(self, init_dict)
for key, value in init_dict.items():
self.inverse[value] = key
def __setitem__(self, key, value):
dict.__setitem__(self, key, value)
self.inverse.__setitem__(value, key)
job_bidict = MyBidict({"John":"director", "Mike":"designer", "Anna":"designer", "Lisa":"engineer"})
print(job_bidict["Mike"])
# output => designer
print(job_bidict.inverse["designer"])
# output => Anna
Advantage
It’s relatively easy to implement.
You can customize the specification of the dict such as whether it allows duplicate values or not.
The functions of built-in dict can be still used.
Disadvantage
It takes a little time and effort to implement.
The original function can destroy the specifications of built-in dict.
class MyMainWindow(QMainWindow):
def __init__(self, parent=None):
# omitted
def _generate_ui(self):
main_widget = QWidget()
main_layout = QVBoxLayout()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
self.tree_widget = QTreeWidget()
self.tree_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.tree_widget.setSelectionBehavior(QAbstractItemView.SelectItems)
self.tree_widget.itemDoubleClicked.connect(self._doubleClicked) # connect function to the signal itemDoubleClicked
main_layout.addWidget(self.tree_widget)
def _init_tree_widget(self):
# omitted
def _doubleClicked(self): # this function is executed when an item is double clicked
print("double clicked")
I’d like to clear and redraw widgets in my custom PySide2 UI.
GOAL
Today’s goal is to remove and delete all children widgets from the layout.
Environment
Windows 10 Python 3.8.7 PySide2 (5.15.2)
Method
I created sample widget with clear-all button.
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import Qt, QEventLoop
class MyWidget(QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout)
self._generateUI()
def _generateUI(self):
clear_button = QPushButton("Clear All")
clear_button.clicked.connect(self._clearall)
self.main_layout.addWidget(clear_button)
label = QLabel("Label")
self.main_layout.addWidget(label)
combobox = QComboBox()
combobox.addItems(["comboBox1", "comboBox2"])
self.main_layout.addWidget(combobox)
buttons_widget = QWidget()
buttons_layout = QHBoxLayout()
buttons_widget.setLayout(buttons_layout)
for i in range(3):
button = QPushButton("button"+str(i))
buttons_layout.addWidget(button)
self.main_layout.addWidget(buttons_widget)
def _clearall(self): # the function to clear widgets
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
1. Get all children
To get the list of children in the layout, use count() and itemAt(). Because the itemAt() function returns QLayoutItem not Qwidget, we should get widget by using QLayoutItem.widget() method.
def _clearall(self):
children = []
for i in range(self.main_layout.count()):
child = self.main_layout.itemAt(i).widget()
if child:
children.append(child)
print(child)
# output
# <class 'PySide2.QtWidgets.QPushButton'>
# <class 'PySide2.QtWidgets.QLabel'>
# <class 'PySide2.QtWidgets.QComboBox'>
# <class 'PySide2.QtWidgets.QWidget'>
def _clearall(self):
children = []
for i in range(self.main_layout.count()):
child = self.main_layout.itemAt(i).widget()
if child:
children.append(child)
for child in children:
child.deleteLater()
* There are many ways to remove widget from the parent layout. For example, some people use setParent(None) or something. However don’t use removeWidget() because it just removes widget without destroying it. Please refer to the discussion below.
There are another methods to implement _clearall() function.
The method in which items deleted in for loop
It works well because the deleteLater() destroys items after returning control from the function.
def _clearall(self):
for i in range(self.main_layout.count()):
child = self.main_layout.itemAt(i).widget()
child.widget().deleteLater()
This is a wrong example. The itemAt() function can’t find the child because when an item is deleted with setParent(None), other items will be renumbered.
def _clearall(self): #wrong example
for i in range(self.main_layout.count()):
child = self.main_layout.itemAt(i).widget()
child.setParent(None)
# An error occurred: AttributeError: 'NoneType' object has no attribute 'widget'
f = open('test.txt', 'r')
# getting class name
print(f.__class__.__name__)
#output => TextIOWrapper
# getting the module where the class is defined
print(f.__class__.__module__)
#output => _io
f.close()
def import_n_th_module(index):
"""
the function to get index number and import the module named mymodule+index such as mymodule1, mymodule2,...
"""
module_name = 'mymodule' + str(index)
# import module_name => the error occured
The purpose of the importlib package is two-fold. One is to provide the implementation of the import statement (and thus, by extension, the import() function) in Python source code. <omit>
I created 3 modules in modules package. And each module prints “[module_name] is imported” when being imported.
#test.py
import sys
import importlib
index = 3
importlib.import_module('modules.mymodule' + str(index))
# output => "mymodule3 is imported"
Postscript
Another purpose of the imporlib
Two, the components to implement import are exposed in this package, making it easier for users to create their own custom objects (known generically as an importer) to participate in the import process.
def get_keyconfigs():
"""
:return: dict{kerconfig_name(str): path to config file(str)}
"""
config_pathes = bpy.utils.preset_paths("keyconfig")
config_dict = {}
for config_path in config_pathes:
for file in os.listdir(config_path):
name, ext = os.path.splitext(file)
if ext.lower() in [".py", ".xml"] and not name[0] == ".":
config_dict[name] = os.path.join(config_path, file)
return config_dict
2.2 Activate specified keyconfig and return active keyconfig
Activate the specified keyconfig by its path and get active keyconfig with wm.keyconfigs.active then restore active keyconfig to original settings.
def get_keyconfig(keyconfig_name, keyconfig_dict):
wm = bpy.context.window_manager
if keyconfig_name in wm.keyconfigs.keys(): #if the keyconfig can be found, return it
return wm.keyconfigs[keyconfig_name]
else: # activate by config path and return it
keyconfig_path = keyconfig_dict[keyconfig_name]
current_path = keyconfig_dict[wm.keyconfigs.active.name]
bpy.ops.preferences.keyconfig_activate(filepath=keyconfig_path)
kc = wm.keyconfigs.active
bpy.ops.preferences.keyconfig_activate(filepath=current_path)
return kc