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.