Reputation: 174
(sample problematic code and its output at the bottom)
Using PyQt5, I'm writing a QDialog
with QTabWidget
in it.
This tab widget has a QFormLayout
layout.
I want to iterate over the form and store its right-side widgets (the "fields") as keys in a weakref.WeakKeyDictionary
for later use (until the dialog is closed and the respective keys hopefully vanish automatically).
I found out that my weak-keys dict don't work as expected: some widgets are correctly stored, some are not. In particular, widgets added later seem to be stored more often (when I quit and reopen the app many times).
I called print(hex(id(label))
for each label widget label
in the form.
This showed that some labels have the same Python id
, and I believe that only the last iterated over widget with any particular id is being stored.
Is this "id sharing" really the reason why my weak-keys dict doesn't store all widgets? Why doesn't each widget have its own id? Can I change my code such that each widget has a unique id? Can I change my code such that each widget can be stored exactly once in my weak-keys dict?
Sample code:
#!/usr/bin/env python3
from weakref import WeakKeyDictionary
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.weak = WeakKeyDictionary()
self.clickme = QPushButton("Click me", self)
self.clickme.clicked.connect(self.open_settings)
@pyqtSlot()
def open_settings(self) -> None:
dialog = QDialog(self)
self.weak[dialog] = "Settings"
grid = QGridLayout()
tabs = QTabWidget(dialog)
tab0 = QWidget(tabs)
tabs.addTab(tab0, "Sample")
form0 = QFormLayout()
for char in "sample":
form0.addRow(char, QLineEdit(tab0))
tab0.setLayout(form0)
# print information
for row in range(form0.rowCount()):
label = form0.itemAt(row, form0.LabelRole).widget()
print(hex(id(label)), type(label).__name__)
self.weak[label] = "foobar"
print(flush=True)
for k, v in self.weak.items():
print(f"{k!r}: {v!r}")
print(flush=True)
grid.addWidget(tabs, 0, 0, 1, 3)
dialog.show()
dialog.exec()
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
Output of this sample code when the whole app is running:
0x7f5956285670 QLabel # this is ok
0x7f5956285700 QLabel # this is ok
0x7f59562855e0 QLabel # this is ok
0x7f5956285670 QLabel # why the repeated id?!
0x7f5956285700 QLabel # why the repeated id?!
0x7f59562855e0 QLabel # why the repeated id?!
# the resulting weak-keys dict:
<PyQt5.QtWidgets.QDialog object at 0x7f5956342f70>: 'Settings'
<PyQt5.QtWidgets.QLabel object at 0x7f59562855e0>: 'foobar'
Upvotes: 2
Views: 754
Reputation: 243973
It seems that it is a bug and it seems to happen when the objects are created in C++ (like the QWidgetItem of the QFormLayout) and then when it is accessed from python, seems that pyqt5 reuses the objects (in pyside2 it does not happen).
A possible solution is to create the QLabels in python so that the objects are not reused.
form0.addRow(QLabel(char), QLineEdit())
Besides that you have to access the QLabel instead of the QWidgetItem:
# print information
for row in range(form0.rowCount()):
label = form0.itemAt(row, form0.LabelRole).widget()
print(hex(id(label)), type(label).__name__)
self.weak[label] = "foobar"
print(flush=True)
Output:
0x7f756703c0d0 QLabel
0x7f756703c1f0 QLabel
0x7f756703c310 QLabel
0x7f756703c430 QLabel
0x7f756703c550 QLabel
0x7f756703c670 QLabel
<PyQt5.QtWidgets.QDialog object at 0x7f756ef61dc0>: 'Settings'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c0d0>: 'foobar'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c1f0>: 'foobar'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c310>: 'foobar'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c430>: 'foobar'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c550>: 'foobar'
<PyQt5.QtWidgets.QLabel object at 0x7f756703c670>: 'foobar'
Upvotes: 2