The concept of signals and slots is possibly
the most interesting innovation in the Qt library. Good widgets
and a clean API are rare, but not unique. But until Qt appeared on
the horizon, connecting your widgets with your application and
your data was a nasty and error-prone endeavor — even in
Python. I will first discuss the problem that is solved by signals
and slots in some detail; then I will introduce the actual
mechanics of the signal/slot mechanism, and finish with an
application of the technique outside the GUI domain.
The concept of signals and slots
The problem in a nutshell: imagine you have
an application, and the application shows a button on screen.
Whenever the button is pressed, you want a function in your
application to execute. Of course, you'd prefer that the button
doesn't know much about the application, or you would have to
write a different button for each application. In the next
example, the button has been coded to work only with an
application that has the
doSomeApplicationSpecificFunction function.
Example 7-1. A stupid button which is not reusable
#
# stupid_button.py — this button is not reusable
#
class Button:
def __init__(self, application):
self.application = application
def clicked(self):
self.application.doSomeApplicationSpecificFunction()
class Application:
def __init__(self):
self.button=Button(self)
def doSomeApplicationSpecificFunction(self):
print "Function called"
app=Application()
app.button.clicked() # simulate a user button press
Callbacks
This is no solution— the button code
isn't reusable at all. A better solution would be to pass the
function object to the button. Remember
that in Python functions are objects just like everything
else. In a language like C or C++ you would pass a
function pointer, the actual memory
address of the function being called. This is quite nasty,
because there is no way the compiler can check what arguments
are passed to the function represented by the function
pointer. In Python, passing functions around is really
easy.
Example 7-2. A simple callback system
#
# callback.py — handing the function over the the app
#
class Button:
def __init__(self, function):
self.callbackFunction = function
def clicked(self):
apply(self.callbackFunction)
class Application:
def __init__(self):
self.button=Button(self.doSomeApplicationSpecificFunction)
def doSomeApplicationSpecificFunction(self):
print "Function called"
app=Application()
app.button.clicked() # simulate a user button press
Using apply() to execute
function objects.: Note the usage of the
apply() function in the
clicked() function — this
Python built-in function executes the function object you
pass as the first argument argument. You can also hand it
parameters, as a tuple in the second argument to
apply(). You'll see that idiom quite
often when we subclass Qt classes:
class MyWidget(QWidget):
def __init__(self, *args):
apply(QWidget.__init__, (self,) + args)
This is useful because
QWidget and the other Qt classes
often have a lot of optional parameters, such as the
object name or certain widget flags. If we discount the
possibility that someone wants to use those optional
parameters, we would write:
class MyWidget(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
This is far less flexible. In the previous example,
we created an argument tuple to be passed to the
__init__() by first creating a tuple
containing our own object reference -
self, and then adding the arguments
from the variable positional argument list to that tuple.
Remember from the discussion of positional arguments in
the Section called Methods and functions in Chapter 4 that the
arguments in *args are a tuple, and
you can create a new tuple by adding two tuples.
In more recent versions of Python, you don't need to
use apply() anymore to call the
constructor of a superclass with a variable number of
arguments. That is, from version 2.0 of Python you can
also use the following construction:
>>> class O(QObject):
... def __init__(self, *args):
... QObject.__init__(self, *args)
...
>>> a=O()
>>> b=O(a, "bla")
>>> b
<__main__.O instance at 0x82b5c3c>
>>> b.name()
'bla'
>>> b.parent()
<__main__.O instance at 0x8106cb4>
>>>
That is, when calling the constructor of the
superclass, you can pass self as the
first argument, and then the argument list, with asterisks
and all.
Action registry
Unfortunately, this callback system is not
quite generic enough. For example, what if you wanted to
activate two functions when the button is pressed? While this
is not likely in the simple example, under more complex
situations it often occurs. Think of a text editor where
editing the text should change the internal representation of
the document, the word count in the statusbar, and the
edited-indicator in the titlebar. You wouldn't want to put all
this functionality in one function, but it is a natural fit
for signals and slots. You could have one signal,
textChanged, that is connected to three
functions: changeText(),
setWordCount(),
setEdited().
Wouldn't it be extremely comfortable to
simply have a central registry where interested parties could
come together? Something like:
Example 7-3. A central registry of connected widgets
#
# registry.py — a central registry of connected widgets
#
class Registry:
def __init__(self):
self.connections={}
def add(self, occasion, function):
if self.connections.has_key(occasion) == 0:
self.connections[occasion]=[function]
else:
self.connections[occasion].append(function)
def remove(self, occasion, function):
if self.connections.has_key(occasion):
self.connections[occasion].remove(function)
def execute(self, occasion):
if self.connections.has_key(occasion):
for function in self.connections[occasion]:
apply(function)
registry=Registry()
class Button:
def clicked(self):
registry.execute("clicked")
class Application:
def __init__(self):
self.button=Button()
registry.add("clicked", self.doAppSpecificFunction)
registry.add("clicked", self.doSecondFunction)
def doAppSpecificFunction(self):
print "Function called"
def doSecondFunction(self):
print "A second function is called."
app=Application()
app.button.clicked()
The actual registry is a Python dictionary with
the name connections. Here,
each occasion is used as a key to
find the actual function object that should be called.
If the ‘occasion' is already registered, we simply
add a new entry to the list; otherwise a new entry
is created in the registry.
If the ‘occasion' exists, then we remove the
relevant function entry from its list of
functions.
We loop over all functions that belong to this
‘occasion' and simply execute them by calling
apply() on them.
A registry is a unique object to an application:
there should only be one, so we create it globally.
This is the ‘button' class.
Whenever the button is ‘clicked', it calls the
execute() function in the
registry with the ‘clicked' occasion.
The application creates one button and binds two
of its functions to the button. This looks a
lot like the way connections are made in Qt!
Here we simulate a button click by directly
calling the clicked() function on
the button.
This is one step up from the previous example, which was
an extremely crude implementation of the well known Observer
design pattern, in that there is now a ‘neutral' object that
mediates between the button and the application. However, it
is still not particularly sophisticated. It certainly wouldn't do
for a real application — where there might be many
objects with the same ‘occasion'.
It is quite possible to implement a solution like this in
pure Python, especially with the weak references module that
debuted in Python 2.1. Bernhard Herzog has done so in his fine
Python application Sketch
(http://sketch.sourceforge.net). He had to do it himself
— because he was working in PyGTK, not PyQt.
Fortunately, PyQt has already solved the whole problem for
us.
Signals and slots
We've just outlined the problem which the
developers of Qt at Trolltech have solved in a unique and
flexible manner. They created the concept of signals and
slots. signals are sent by an object that
wants to tell the world something interesting has happened,
and by connecting the "signals to the
slots", those signals arrive at the slots
of the objects that are interested.
On the whole the concept is really neat
and clean and the implementation well-executed. What's more,
the concept is even better suited to Python than it is to C++.
If you want to use signals and slots in C++, you have to work
with a preprocessor, called moc, and
indicate with special macros which function can be called as a
slot, and which function is a signal. All that is completely
unnecessary in Python, where a signal is a string, and any
function can be a slot.
Signals and slots are not magic, of
course. As with our simple Python registry, there has to be a
registry of objects that are interested in signals. This
registry is updated by the connect and
disconnect functions; both are member
functions of the QObject class. The
registry, as far as Python signals and slots is concerned, is
kept by the sip library. Signals and slots that are defined in
the underlying C++ library are maintained by the
QObject class itself.
In a nutshell, signals and slots are the
solution Qt provides for situations in which you want two
objects to interact, while keeping that fact hidden from
them.
Signals, messages, events: This is one area where there is a
perfect Babel of tongues. Even really knowledgeable people
like Dr Dobbs' Al Stevens get confused when confronted with
terms like ‘message', ‘event' or
‘signal'.
In PyQt programming, the term
'‘message' is quite irrelevant — it is used in
Windows programming to indicate function calls made from
your application to the Windows GUI libraries.
Events and signals, on the other hand,
are central to PyQt. Signals and slots are used to connect
one object to another. An example is the perennial
pushbutton, whose clicked() signal gets
connected to the accept() slot function
of a dialog box. Signals are used to connect entities
internal to the application.
Events are more often generated
directly by user input, such as moving or clicking with the
mouse, or typing at the keyboard. As such, they don't
connect two class instances, but rather a physical object,
such as a keyboard, with an application. Events are
encapsulated by the QEvent class, and
are mostly delivered to QWidget and
its descendants. Events are used to communication with
external entities.