Pyqt5 deleteLater() VS sip.delete() - python

I want to understand what is the difference between deleting a widget(include it's layout and children in this layout) using sip.delete() and deleteLater(). I know that removeWidget() and setParent(None) is just removing the widget from layout, but it's not deleting the object itself from the memory. If I want to delete an object from a memory, which one shall I use? I know this question is asked before, but I hope to get detailed answer:)

I recommend you read this answer since I will use several concepts explained there.
The sip.delete() is used to directly invoke the destructor of the C++ object from the wrapper, something like:
delete wraper_instance->_cpp_object;
Instead deleteLater() is a method of the QObjects that sends an event so that the eventloop calls the destructor of the C++ object, something like:
post QDeferredDeleteEvent.
Run all pending events.
Destroy the object.
emit the destroyed signal.
Why do QObjects have as an alternative to deleteLater()? Well, directly deleting a QObject can be unsafe, for example let's assume that some QWidget (which is a QObject) is deleted invoking the destructor directly but a moment before in another part of the application it asks to update the entire GUI, as the GUI is not notified removing the object will then cause unallocated memory to be accessed causing the application to crash.
Therefore if you want to delete a QObject then it is safer to use deleteLater(), for other C++ objects (like QImage, QPixmap, QGraphicsItems, etc) you should use sip.delete().
For more information read:
https://doc.qt.io/qt-5/qobject.html#dtor.QObject
https://doc.qt.io/qt-5/qobject.html#deleteLater

Related

Python PyQt6 deleteLater() and child widgets

I have a question of functionality that I am uncertain about. I'm not stuck on code implementation, so there is no need to share code for this question. I use
layout.removeWidget
widget.deleteLater()
a couple/few times in a project I'm building. I once read here on SO that deleteLater() does not function as described when deleting a widget with child widgets. The comment said that child widgets do not get deleted correctly, and will stay in memory, which could lead to memory bloat/leaks. Simply put (my question), is this true?
The docs mention nothing of this that I could find. And it was just one comment from years back, that was actually written for PyQt5 (or 4), I believe. So, have any of you guys done any tests on this? Is this a bug in older versions of PyQt that has been fixed, or was the commentor outright wrong?
As you can see from my question, my issue isn't how to write something, it's about behind the scenes functionality of deleteLater().
First of all, it is important to remember that PyQt (as PySide) is a binding to Qt, which is written in C++.
All Qt objects and functions are accessed from python using wrappers.
When a Qt object is created from Python, there are actually two objects:
the C++ object;
the python object that allows us to use the above Qt object;
The lifespan of those two objects may not always coincide. In fact, the C++ object can be destroyed while the python reference remains, or the other way around.
C++ object destruction
deleteLater() is guaranteed to destroy the C++ objects along with any object for which they have ownership (including indirect ownership for grand[grand, ...]children). Note that the object is not destroyed immediately, but only as soon as control returns to the event loop: then the object emits the destroyed() signal and after that all its children are destroyed along with it, recursively.
This will not delete the python reference, if it existed. For instance:
class MyLabel(QLabel):
def __init__(self, value, parent=None):
super().__init__(value, parent)
self.value = value
If you keep a python reference to an instance of the class above, you will still be able to access its self.value even after the object has been destroyed with deleteLater(). If you try to access any Qt functions or properties, instead, you'll get an exception:
>>> print(mylabel.value)
'some value'
>>> print(mylabel.text())
RuntimeError: wrapped C/C++ object of type MyLabel has been deleted
So, the python object will obviously keep using memory resources until it get garbage collected (its reference count becomes 0). This can be a problem if you keep references to objects that have large memory footprint on their python side (i.e., a large numpy array).
Python object destruction
Deleting a python reference doesn't guarantee the destruction of the Qt object, as it only deletes the python object. For instance:
>>> parent = QWidget()
>>> label = MyLabel('hello', parent)
>>> parent.show()
>>> del label
>>> print(parent.findChild(QLabel).text())
'hello'
This is because when an object is added to a parent, that parent takes ownership of the child.
Note that Qt objects that have no parent will also get destroyed when all python references are destroyed as well.
That is the reason of one of the most common questions here on SO, when somebody is trying to create another window using a local reference and without any parent object, but the window is not shown because it gets immediately garbage collected.
Note that this is valid for Qt6 as it is for Qt5 and as it was for Qt4, and it is also for their relative python bindings.
Considering what written in the first part:
>>> parent = QWidget()
>>> label = MyLabel('hello', parent)
>>> parent.show()
>>> del parent
>>> print(label.value)
'hello'
>>> print(label.text())
RuntimeError: wrapped C/C++ object of type MyLabel has been deleted
So, what must be always kept in mind is where objects live, including their C++ and python properties and attributes. If you're worried about memory usage for standard (not python-subclassed) widgets, deleteLater() is always guaranteed to release the memory resources of those widgets, and you only need to ensure that no remaining python reference still exists (but that would be true for any python program).
Finally, some considerations:
in some very complex situations it's not always possible to keep track of all python references, so it's possible that even if you destroyed a parent, the references to some children objects still exist;
not all Qt objects are QObjects, and their usage, behavior or memory management depends both on Qt and the C++ implementation; those object can be sometimes destroyed when their python references are, or when Qt "decides so";
not all QObjects take ownership of other QObjects when they are "added" to them: one common case is QAction, which can be shared among many objects (QMenu, QMenuBar, QToolBar, and even standard QWidgets); always look for the "...takes ownership" phrase in the documentation whenever in doubt;

Accessing a common data object in a PyQt application

I have a GUI programmed in PyQt with many widgets and different windows, etc. The data for the GUI is stored in a python object. This object should be reachable from every widget in the program. Where should I put this object?
Right now I have it in the QMainWindow instance that is used for the program's main window. The problem is that it is hard to reach the QMainWindow object from deeply nested widgets. It seems much simpler to use the QApplication instance for that, because you can get it with QtCore.QCoreApplication.instance() (as shown in this answer). However, I couldn't find any other examples encouraging you to change the QApplication class, so I wonder if it really should be used that way.
What approach would you suggest?
The correct approach is to put this data/settings object in a separate module. This can then be simply imported wherever it is needed. Ideally, the module (and the code which creates the data/settings object) should be largely independent from the rest of the application.
There is no real value in tying the data/settings object to the application instance, since you would still have to import either QApplication or qApp to access it anyway. And the same thing applies to the QMainWindow - it just moves the problem to a different location, and adds an unnecessary layer of indirection. Another big problem with this approach is that the data/settings object cannot be accessed until an instance of the application or main window becomes available. Very often, the data/settings object will be required during the initialisation of various parts of the application, so tying it to specific GUI elements can easily lead to circular dependencies, or other ordering problems.
I suppose the key design principle here is Loose Coupling: once you've decoupled your data/settings object from the GUI, the rest of the application can access it wherever and whenever it is required.

PyQt - parent for widgets - necessary?

I tried creating GUI with a few widgets, all of them without secifying a parent.
It worked fine.
Is this Ok, or there is reason to specify the parent?
Thanks!
In general, it's better to specify a parent wherever possible, because it can help avoid problems with object cleanup. This is most commonly seen when exiting the program, when the inherent randomness of the python garbage-collector can mean objects sometimes get deleted in the wrong order, causing the program to crash.
However, this find of problem does not usually affect the standard GUI widgets, because Qt will automatically reparent them once they have been added to a layout. The more problematic objects are things like item-models, item-delegates, graphics-scenes, etc, which are closely linked to a view.
Ideally, a pyqt program should have one root window, with all the other objects connected to it in a parent-child hierarchy. When the root is deleted/closed, Qt will recursively delete all its child objects as well. This should leave only the pyqt wrapper objects behind, which can be safely left to the python garbage-collector to clean up.
A more constructive benefit of specifying parents, is that it simply makes objects more accessible to one another. For instance, a common idiom is to iterate over a group of buttons via their parent:
for button in parent.findChildren(QAbstractButton):
print(button.text())

What are good practices for avoiding crashes / hangs in PyQt?

I love both python and Qt, but it's pretty obvious to me that Qt was not designed with python in mind. There are numerous ways to crash a PyQt / PySide application, many of which are extraordinarily difficult to debug, even with the proper tools.
I would like to know: what are good practices for avoiding crashes and lockups when using PyQt and PySide? These can be anything from general programming tips and support modules down to highly specific workarounds and bugs to avoid.
General Programming Practices
If you must use multi-threaded code, never-ever access the GUI from a non-GUI thread. Always instead send a message to the GUI thread by emitting a signal or some other thread-safe mechanism.
Be careful with Model/View anything. TableView, TreeView, etc. They are difficult to program correctly, and any mistakes lead to untraceable crashing. Use Model Test to help ensure your model is internally consistent.
Understand the way Qt object management interacts with Python object management and the cases where this can go wrong. See http://python-camelot.s3.amazonaws.com/gpl/release/pyqt/doc/advanced/development.html
Qt objects with no parent are "owned" by Python; only Python may delete them.
Qt objects with a parent are "owned" by Qt and will be deleted by Qt if their parent is deleted.
Example: Core dump with PyQt4
A QObject should generally not have a reference to its parent or any of its ancestors (weak references are ok). This will cause memory leaks at best and occasional crashes as well.
Be aware of situations where Qt auto-deletes objects. If the python wrapper has not been informed that the C++ object was deleted, then accessing it will cause a crash. This can happen in many different ways due to the difficulty PyQt and PySide have in tracking Qt objects.
Compound widgets such as a QScrollArea and its scroll bars, QSpinBox and its QLineEdit, etc. (Pyside does not have this problem)
Deleting a QObject will automatically delete all of its children (however PyQt usually handles this correctly).
Removing items from QTreeWidget will cause any associated widgets (set with QTreeWidget.setItemWidget) to be deleted.
# Example:
from PyQt4 import QtGui, QtCore
app = QtGui.QApplication([])
# Create a QScrollArea, get a reference to one of its scroll bars.
w = QtGui.QWidget()
sa = QtGui.QScrollArea(w)
sb = sa.horizontalScrollBar()
# Later on, we delete the top-level widget because it was removed from the
# GUI and is no longer needed
del w
# At this point, Qt has automatically deleted all three widgets.
# PyQt knows that the QScrollArea is gone and will raise an exception if
# you try to access it:
sa.parent()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: underlying C/C++ object has been deleted
# However, PyQt does not know that the scroll bar has also been deleted.
# Since any attempt to access the deleted object will probably cause a
# crash, this object is 'toxic'; remove all references to it to avoid
# any accidents
sb.parent()
# Segmentation fault (core dumped)
Specific Workarounds / Bugs
Changing the bounds of QGraphicsItems without calling prepareGeometryChange() first can cause crash.
Raising exceptions inside QGraphicsItem.paint() can cause crashes. Always catch exceptions inside paint() and display a message rather than letting the exception proceed uncaught.
QGraphicsItems should never keep a reference to the QGraphicsView they live in. (weakrefs are ok).
Using QTimer.singleShot repeatedly can cause lockups.
Avoid using QGraphicsView with QGLWidget.
Practices for Avoiding Exit Crashes
QGraphicsItems that are not part of a QGraphicsScene can cause crash on exit.
QObjects that reference their parent or any ancestor can cause an exit crash.
QGraphicsScene with no parent can cause an exit crash.
The easiest way to avoid exit crashes is to call os._exit() before python starts collecting Qt objects. However, this can be dangerous because some part of the program may be relying on proper exit handling to function correctly (for example, terminating log files or properly closing device handles). At a minimum, one should manually invoke the atexit callbacks before calling os._exit().
Just adding to the point:
If you must use threads in your qt-based program, you really must disable the automatic garbage collector and do manual collections on the main thread (as described in http://pydev.blogspot.com.br/2014/03/should-python-garbage-collector-be.html) -- note that you should do that even if you make sure your objects don't have cycles (with a cycle you basically make your objects live until the python cyclic garbage collector bumps in, but sometimes if you have something as an exception it's possible that a frame is kept alive, so, in such a situation your object may still be kept alive longer than you anticipate)... in those cases, it's possible that the garbage collector bumps in in a secondary thread, which may cause qt to segfault (qt widgets must always be collected in the main thread).
Just for the reference, I post the comment from the author of PyQt to the answer by Luke: "It's rubbish."
I think it's important because somebody can jump into this post and remained perplexed with all these (nonexistent) "problems".
Emitting signals instead of synchronic UI control was the key to avoid problems for me during implementation of logic circuit simulator

Synchronized Qt TreeWidgets

I'm new to Python and PyQt. What is the best way to keep 4 QtTreeWidgets synchronized so that the items are the same as well as all the attributes of all the items? These widgets appear in different dialog boxes at different times during a session. For a number of reasons, I need to keep as much of the existing code, signals and layout as intact as possible. The Model/View division would be the obvious first place to go, but I don't want to touch any of the methods that are used to access or update the tree. I'm planning to refactor the whole thing in a few months, but I need something quickly to carry me until then.
Since each QTreeWidget is a convenience class, each has its own data. The UI is maintained in Qt Designer and I don't want to keep it that way.
When each dialog is initialized, the tree appears. The application has a singleton class that all dialogs can use to reference its variables/attributes.
In the initialization of each parent dialog, couldn't I check to see if a 'locationTree' attribute exists in the singleton. If not, I would need to populate it with its initial state and have the tree in the dialog use it or a copy of it. Any time the state of the dialog tree is altered in ways that I can trap, I'd like to update the singleton 'locationTree' to mirror the change. Although there's a clone method on a QTreeWidgetItem, I didn't see a corresponding method for the entire QTreeWidget.
How can I accomplish this with the least amount of change to the existing code base and GUI layout?
John
Yes using the MVC facilities is the way to go ...
Even though you are using QTreeWidget you are still working with a class derived from QAbstractItemView therefore the model() and setModel() calls are available. Take a model from one of the widgets that you are creating and then set it in the other widgets. Whenever you change the data in one of the widgets the other widgets will follow suit as they are using the same instance of model.
If you need to maintain the same selection state in all for widgets (which parts of the tree are open or close) that might be a little bit harder but it might actually work by using the same selectionModel selectionModel() and setSelectionModel()
I'm sure you're right that using Model/View is the best approach.
But without an idea of roughly how many items your tree widgets will have, and how frequently they'll be updated, it's hard to weigh up alternative approaches. Also, what version of Qt are you using?
If the number of updates and items are not huge, one approach is to introduce a class that inherits QObject (so it has signals and slots), and make it responsible for keeping all your QTreeWidgets in sync.
By connecting signals and slots for each QTreeWidget to a single other object, you avoid the nightmare of having every tree widget know about every other one.

Categories

Resources