PyQt Quickie: Don't Get Garbage Collected
There is one area where Qt and Python (and in consequence PyQt) have major disagreements. That area is memory management.
While Qt has its own mechanisms to handle object allocation and disposal (the hierarchical QObject trees, smart pointers, etc.), PyQt runs on Python, so it has garbage collection.
Let's consider a simple example:
from PyQt4 import QtCore def finished(): print "The process is done!" # Quit the app QtCore.QCoreApplication.instance().quit() def launch_process(): # Do something asynchronously proc = QtCore.QProcess() proc.start("/bin/sleep 3") # After it finishes, call finished proc.finished.connect(finished) def main(): app = QtCore.QCoreApplication([]) # Launch the process launch_process() app.exec_() main()
If you run this, this is what will happen:
QProcess: Destroyed while process is still running. The process is done!
Plus, the script never ends. Fun! The problem is that proc
is being deleted at the end of launch_process
because there are no more references to it.
Here is a better way to do it:
from PyQt4 import QtCore processes = set([]) def finished(): print "The process is done!" # Quit the app QtCore.QCoreApplication.instance().quit() def launch_process(): # Do something asynchronously proc = QtCore.QProcess() processes.add(proc) proc.start("/bin/sleep 3") # After it finishes, call finished proc.finished.connect(finished) def main(): app = QtCore.QCoreApplication([]) # Launch the process launch_process() app.exec_() main()
Here, we add a global processes
set and add proc
there so we always keep a reference to it. Now, the program works as intended. However, it still has an issue: we are leaking QProcess
objects.
While in this case the leak is very short-lived, since we are ending the program right after the process ends, in a real program this is not a good idea.
So, we would need to add a way to remove proc
from processes
in finished
. This is not as easy as it may seem. Here is an idea that will not work as you expect:
def launch_process(): # Do something asynchronously proc = QtCore.QProcess() processes.add(proc) proc.start("/bin/sleep 3") # Remove the process from the global set when done proc.finished.connect(lambda: processes.remove(proc)) # After it finishes, call finished proc.finished.connect(finished)
In this version, we will still leak proc
, even though processes
is empty! Why? Because we are keeping a reference to proc
in the lambda
!
I don't really have a good answer for that that doesn't involve turning everything into members of a QObject
and using sender
to figure out what process is ending, or using QSignalMapper
. That version is left as an exercise.
Algo que me pasó al principio, es cuando se crean ventanas que se quieren mostrar, pero se asignan a variables que no existen fuera del scope del metodo tambien y por ahi te matas debugueando de por que la ventana no se abre, y en realidad se esta abriendo y destruyendo antes de que te des cuenta :P
hold=[proc]
proc.finished.connect(finished)
proc.finished.connect(lambda: hold.pop() )
If you do this, then you don't need to have the global processes set, either.
Good idea!
I have not tested it, but there is an additional problem that may be an issue here: the order of execution of the slots is not guaranteed. If the lambda is called before finished, proc will be GCd before finished is called, which may cause problems depending on what finished does.
I guess in that case you could write:
hold = [proc]
def callback():
finished()
hold.pop()
proc.finished.connect(callback)
to enforce the call order. Alternatively you could rely on the cyclic garbage collector in 2.7+ to collect the reference cycle created by the lambda. However it may be that, because the reference to the lambda is held by PyQt, the traversal requirements for the collector aren't satisfied in this case, and the cycle may be uncollectable.