documentacion - pyqt pip
PyQt4 setParent vs deleteLater (1)
Tengo un diseño que agrego muchos widgets personalizados con algo como layout.addWidget(widget)
. Más tarde quiero eliminar todos esos widgets personalizados y agregar nuevos. Estoy confundido sobre la mejor manera de hacer esto cuando se trata de deleteLater
y setParent(None)
. Por ejemplo, aquí está mi función de limpieza que se llama para todos los widgets en el diseño:
def _removeFilterWidgetFromLayout(self, widget):
"""Remove filter widget"""
self._collection_layout.removeWidget(widget)
widget.setParent(None)
widget.deleteLater()
Sospecho que no todas estas líneas son necesarias para limpiar adecuadamente la memoria utilizada por el widget. No estoy seguro de qué está pasando con las referencias de C ++ y Python al widget
.
Esto es lo que entiendo sobre las referencias de C ++ y Python a QObjects en PyQt.
Creo que cuando agrega un widget a un diseño, el widget se convierte en un elemento secundario del diseño. Entonces, si llamo removeWidget
, la relación principal se rompe, así que tengo que limpiar la referencia de C ++ y Python porque el widget no tiene otro padre. La llamada a setParent
es una forma explícita de eliminar la relación principal con el diseño. La llamada a deleteLater
está destinada a cuidar la referencia de C ++.
La referencia de Python es basura recolectada porque la variable del widget
fuera del alcance y no hay otros objetos de Python que apunten al widget
.
¿Debo llamar a setParent
y deleteLater
o deleteLater
lo suficiente para limpiarlo adecuadamente?
Como nota al margen, he encontrado que llamar a setParent(None)
es una llamada a función muy costosa en este escenario. Puedo acelerar mucho mi proceso de limpieza al eliminar esta llamada. No estoy seguro si deleteLater
es suficiente para limpiar todo correctamente. Aquí está mi salida de perfil de line_profiler
:
Line # Hits Time Per Hit % Time Line Contents
==============================================================
2167 @profile
2168 def _removeFilterWidgetFromLayout(self, widget):
2169 """Remove filter widget"""
2170
2171 233 1528 6.6 1.0 self._collection_layout.removeWidget(widget)
2172 233 143998 618.0 97.9 widget.setParent(None)
2173 233 1307 5.6 0.9 widget.deleteLater()
Cuando se usa PyQt4, ¿hay una forma "aceptada" de hacer esta limpieza? ¿Debo usar setParent
, deleteLater
o ambos?
Probablemente, la forma más fácil de ver lo que está sucediendo en realidad es pasar por las cosas en una sesión interactiva:
>>> parent = QtGui.QWidget()
>>> child = QtGui.QWidget()
>>> layout = QtGui.QHBoxLayout(parent)
>>> layout.addWidget(child)
>>> child.parent() is layout
False
>>> child.parent() is parent
True
Por lo tanto, el diseño no se convierte en el elemento principal del widget. Esto tiene sentido, porque los widgets solo pueden tener otros widgets como padres y los diseños no son widgets. Todos los widgets añadidos a un diseño eventualmente tendrán sus padres restablecer al padre del diseño (siempre que reciba uno).
>>> item = layout.itemAt(0)
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>
>>> item.widget() is child
True
Como no existe una relación padre / hijo entre los diseños y los widgets que contienen, se necesita una API diferente para acceder a los objetos subyacentes. Los elementos son propiedad del diseño, pero la propiedad de los objetos subyacentes permanece sin cambios.
>>> layout.removeWidget(child)
>>> child.parent() is parent
True
>>> layout.count()
0
>>> repr(layout.itemAt(0))
''None''
>>> item
<PyQt4.QtGui.QWidgetItem object at 0x7fa1715fe318>
En este punto, el diseño ha eliminado su elemento (porque tenía la propiedad del mismo) y, por lo tanto, ya no contiene ninguna referencia al widget contenido. Dado esto, ya no es seguro hacer mucho con el contenedor de Python para el elemento (el intérprete probablemente se bloquee si tratamos de llamar a cualquiera de sus métodos).
>>> child.deleteLater()
>>> parent.children()
[<PyQt4.QtGui.QHBoxLayout object at 0x7fa1715fe1f8>]
>>> child.parent()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: wrapped C/C++ object of type QWidget has been deleted
>>>
Como todavía tenemos la propiedad del widget hijo, podemos llamar deleteLater
en él. Y como se puede ver en el rastreo, esto eliminará el objeto subyacente de C ++, pero su objeto envoltorio de Python quedará atrás. Sin embargo, este contenedor (eventualmente) será eliminado por el recolector de elementos no utilizados, una vez que las referencias restantes de python hayan desaparecido. Tenga en cuenta que nunca es necesario llamar a setParent(None)
durante este proceso.
Un último punto: la sesión de intérprete anterior es ligeramente engañosa, porque la cola de eventos se procesa cada vez que se ejecuta una línea. Esto significa que los efectos de deleteLater
se ven de inmediato, lo que no sería el caso si el código se ejecutara como un script. Para obtener una eliminación inmediata en un script, necesitarás usar el módulo sip
:
>>> import sip
>>> sip.delete(child)