References and ownership

Let's investigate the actual creation and deletion of object - both Python and Qt have a role to play here - a role they mostly perform without surprising the programmer. Still, there are circumstances you should be aware of.

Example 9-3. qtrefs1.py — about Qt reference counting

#
# qtrefs1.py
#

import sys
from qt import *

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        topbutton=QPushButton("A swiftly disappearing button", None)
        topbutton.show()

def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()")
                , app
                , SLOT("quit()")
                )
    app.exec_loop()

if __name__=="__main__":
        main(sys.argv)
      

Here, we create a window, and in the constructor (the __init__ method), we create a QPushButton. That button really should appear as a second toplevel window - but it doesn't. The reason is that the only reference to the object is the variable topbutton, and that variable goes out of scope once the constructor method finishes. The reference ceases to exist, and so the object is deleted.

If we want to keep the button alive, we should keep the reference alive. The easiest way to do that is to associate the button more closely with the containing window object. It is customary to refer to the containing object with the variable self. Python passes a reference to an object as the first argument to any instance method. This reference is usually named self.

So, if we adapt the preceding example as follows, we keep the object:

Example 9-4. qtrefs2.py - keeping a Qt widget alive

#
# qtrefs2.py
#

import sys
from qt import *

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        self.topbutton=QPushButton("A nice and steady button",
                                   None)
        self.topbutton.show()

def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()")
                , app
                , SLOT("quit()")
                )
    app.exec_loop()

if __name__=="__main__":
        main(sys.argv)
        

Does this mean that you always need to keep a reference to all Qt objects yourself? This would make creating complex applications quite a drag! Fortunately, sip is more clever than it seems. QObject derived objects stand in a owner-ownee (or parent-child) relation to each other. Sip knows this, and creates references to child objects on the fly, and decreases those references if the parents are deleted. (The Qt library does something similar if you program in C++. This gives a kind of Java-like flavor to C++ which is not appreciated by everyone).

To keep a widget's child alive, enter the parent object in the parent argument of the child constructor, in this case, this is the second argument to the QPushButton constructor:

Example 9-5. qtrefs3.py - Qt parents and children

#
# qtrefs3.py
#

import sys
from qt import *

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        parentedButton=QPushButton("A nice and steady button "
                                  + "that knows its place",
                                  self)
        parentedButton.resize(parentedButton.sizeHint())

def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()"),
                app,
                SLOT("quit()"))
    app.exec_loop()

if __name__=="__main__":
        main(sys.argv)
        

Note however these two important side-effects: The first is that this button, now that it is owned by the main window, appears inside the main window. The second is that you no longer need to explicitly call the function show() on the button.

As another side-effect of explicitly parenting objects, you need to be aware of who owns an object before you can be sure that it will be deleted: your Python application or another Qt object.

The trick is to determine who exactly owns the widget in question. Everything that is derived from QObject has the function parent(), which can be used to determine the owner of a widget. You can use the function removeChild to remove the widget itself. Using parent() is often easier than remembering who exactly owned the widget you want to get rid of.

self.parent().removeChild(self)
    

If you execute this incantation, the poor widget will be orphaned, and a Python del statement on the Python reference will definitively remove the child.

Example 9-6. Eradicating a widget

#
# qtrefs4.py - removing a widget
#

import sys
from qt import *

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        self.parentedButton=QPushButton("A nice and steady button "
                                   + "that knows its place",
                                   self)
        self.parentedButton.resize(self.parentedButton.sizeHint())
        self.connect(self.parentedButton,
                     SIGNAL("clicked()"),
                     self.removeButton)

    def removeButton(self):
        self.removeChild(self.parentedButton)
        del self.parentedButton


def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()"),
                app, SLOT("quit()"))
    app.exec_loop()

if __name__=="__main__":
        main(sys.argv)
      

Pressing the button will remove it, first by removing the ownership relation between win and self.parentedButton and then removing the Python reference to the object.

It is possible to retrieve the children of a certain QObject object by calling children on QObject. Sip is clever enough to return the Python wrapper object associated with that instance (rather than the actual C++ object instance).

Example 9-7. children.py - getting the children from a single parent

#
# children.py
#

import sys
from qt import *

def printChildren(obj, indent):
    children=obj.children()
    if children==None:
        return
    for child in children:
        print indent, child.name(), child.__class__
        printChildren(child, indent + "  ")

class PyPushButton(QPushButton): pass

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        mainwidget=QWidget(self, "mainwidget")
        layout=QVBoxLayout(mainwidget, 2, 2, "layout")
        button1=QPushButton("button1", mainwidget, "button1")
        button2=PyPushButton("button2", mainwidget, "button2")
        layout.addWidget(button1)
        layout.addWidget(button2)

        self.setCentralWidget(mainwidget)
        printChildren(self, "  ")

def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()")
                , app
                , SLOT("quit()")
                )
    app.exec_loop()

if __name__=="__main__":
        main(sys.argv)
        

Running children.py will give the following output:

boudewijn@maldar:~/doc/opendoc > python children.py
   hide-dock qt.QObject
   mainwidget qt.QWidget
     layout qt.QVBoxLayout
     button1 qt.QPushButton
     button2 __main__.PyPushButton
   unnamed qt.QObject
     unnamed qt.QObject
     unnamed qt.QObject
       unnamed qt.QObject
       unnamed qt.QObject
     unnamed qt.QObject

What you cannot see here is the parallel structure of QLayoutItems that proxy for the widgets. For that you need to use the QLayoutIterator that is provided by the iterator() method of QListViewItem. Here, next(), both returns the next item, and moves the iterator onwards.

Example 9-8. Iterating over children

#
# children.py
#

import sys
from qt import *

def printChildren(obj, indent):
    iter = obj.iterator()
    while iter.current():
        print "current:", iter.current()
        print "next:", iter.next()

class PyPushButton(QPushButton): pass

class MainWindow(QMainWindow):

    def __init__(self, *args):
        apply(QMainWindow.__init__, (self, ) + args)
        mainwidget=QWidget(self, "mainwidget")
        layout=QVBoxLayout(mainwidget, 2, 2, "layout")
        button1=QPushButton("button1", mainwidget, "button1")
        button2=PyPushButton("button2", mainwidget, "button2")
        button3=PyPushButton("button3", mainwidget, "button3")
        layout.addWidget(button1)
        layout.addWidget(button2)
        layout.addWidget(button3)
        
        self.setCentralWidget(mainwidget)
        printChildren(layout, "  ")
        
def main(args):
    app=QApplication(args)
    win=MainWindow()
    win.show()
    app.connect(app, SIGNAL("lastWindowClosed()")
                , app
                , SLOT("quit()")
                )
    app.exec_loop()
  
if __name__=="__main__":
        main(sys.argv)
      
boud@calcifer:~/doc/pyqt/src/qt2/ch3 > python layoutchildren.py
current: <qt.QLayoutItem instance at 0x82ba8b4> 
next: <qt.QLayoutItem instance at 0x82ba9dc>
current: <qt.QLayoutItem instance at 0x82ba9dc> 
next: <qt.QLayoutItem instance at 0x82baa8c>
current: <qt.QLayoutItem instance at 0x82baa8c> 
next: None
    

Finally, let's test the ownership rules of Qt and Python objects using the interactive Python interpreter. In the following example, we create an object self.o, owned by PyQt, and then a child object is created, not owned by the instance of class A, but as a Qt child of object self.o. Thus, PyQt owns a and self.o, and Qt owns child, and child doesn't get deleted, even when the Python reference goes out of scope.

>>> from qt import QObject
>>> class A:
...     def __init__(self):
...             self.o=QObject()
...             child = QObject(self.o)
... 
>>> a=A()
>>> print a
<__main__.A instance at 0x821cdac>
>>> print a.o
<qt.QObject instance at 0x821ce04>
>>> print a.o.children()
[<qt.QObject instance at 0x821cf54>]
>>> 
    

On the other hand, the following won't work, because as soon as the execution flow leaves the constructor, o is garbage collected, and child, is then garbage-collected, too, since it isn't owned by a Qt object, and Python doesn't have a reference to it anymore, either.

 >>> class B:
...        def ___init__(self):
...             o=QObject()
...             child = QObject(o)
... 
>>> b=B()
>>> b.o
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: o
    

On the other hand, it isn't necessary to keep a Python reference to all created objects: as long as the ultimate parent object is owned by PyQt, everything will go well:

>>> class C:
...        def __init__(self):
...             self.o = QObject()
...             self.child = QObject(self.o)
... 
>>> c = C()
>>> c.o
<qt.QObject instance at 0x8263f54>
>>> c.o.children()
[<qt.QObject instance at 0x821d334>]
>>> c.child
<qt.QObject instance at 0x821d334>
>>> 

As you see, it isn't necessary to keep a reference to child,- because PyQt is the owner of the first object (because it has no Qt parent but a reference to a Python object) but Qt is the owner of the second widget (because it does have a parent) and so the C++ instance (qt.QObject instance at 0x821d334) is not deleted when the corresponding Python object goes out of scope.

What if your Python class were a subclass of QObject?:

>>> class D(QObject):
...        def __init__(self):
...             QObject.__init__(self)
...             o=QObject(self)
...             child = QObject(o)
... 
>>> d=D()
>>> d.children()
[<qt.QObject instance at 0x821d614>]
>>> d.children()[0].children()
[<qt.QObject instance at 0x821d7c4>]
>>> 
    

As you can see, o doesn't get deleted, nor child - both are owned by Qt and will be deleted as soon as object d is deleted. You can still reach these objects by using the children() function QObject provides.

This layer between Python and Qt is implemented in the sip library — sip not only generates the wrapper code, but is a library in its own right, containing functionality for the passing of object references between C++ and Python.

Sip is also responsible for the reference counting mechanisms. In most cases, Sip is clever enough to closely simulate Python behavior for C++ Qt objects. As you saw in the previous example, contrary to what happens in C++, when you remove the last reference to a C++ object, it will be automatically deleted by Sip.