Luke Lee

Software Engineer

Web + Desktop + Science

Fork me on Github

Connecting PyQt signals with decorators

I've been writing PyQt for several years now and just ran across a QSignalMapper code snippet demonstrating the process of connecting a signal to a slot with a decorator. I suppose this is not a common design within the PyQt community. Is there a reason for this?

Consider the following example of how connect can be used as a decorator:

    # Handle grid clicks here  
    @win.clicked.connect  
    def clicked(index):  
        print "Button at:", index, " - text:", items[index].text()  

It never occurred to me, but the connect method of a pyqtSignal object is almost a decorator by definition [1]:

A decorator is any callable Python object that is used to modify a function, method or class definition. A decorator is passed the original object being defined and returns a modified object, which is then bound to the name in the definition.

So, we can use connect as a decorator, but is it the right way to do signal connection?

Decorator 'surprise'

Notice I said almost because a decorator also returns a new callable. The connect doesn't return anything, technically it returns None. So, using connect as a decorator causes a subtle side-effect that is a bit destructive. The connect decorator in the scenario above took a publicly available function and removed it.

This could be a bit surprising for newcomers. However, I think this scenario provides a good jumping off point for thinking about what decorators really are and how the Decorator Design Pattern works in general. I definitely do not want to imply that the above example is bad, just that it's a bit surprising and subtle.

Lessons

The takeaway from this is that you can connect signals and slots with a decorator. This approach provides a few readability benefits:

However, there are a few downsides as well:

Another restriction that's not immediately obvious is there is no way to use this trick when the function/slot is declared within a class and relies on self. Remember, decorators are evaluated at definition time not runtime. So, the decorator line itself cannot refer to self.

Decorator fail

For example, you cannot use connect to write a decorator that connects to a signal declared within a class. The following example should provide enough context to demonstrate this restriction using the Model-View-Controller design pattern.

https://gist.github.com/durden/5310601

Decision

I like that connect can be used as a decorator and in some instances it makes the code much more readable. The QSignalMapper snippet demonstrates some of the benefits. However, that is an example and not a full, complex application. So, in general I think the downsides of using connect as a decorator outweigh the benefits in most large applications.

However, this is not a black and white scenario. You'll have to carefully think about your scenario and decide which option provides the most benefit [2]. Hopefully now you can clearly see the tradeoffs of this design choice.

Bonus: Verify for yourself

You should try this to convince yourself I'm not lying to you. Run the following example and you'll notice you get a TypeError because the clicked method is None after the decorator:

https://gist.github.com/durden/5310356

[1] Definition of Python decorator

[2] You could write your own decorator that wraps the use of connect and returns a callable. This would avoid the biggest pitfall of connect as a decorator, but in my opinion this adds another layer of abstraction that is unnecessary with something as mundane and simple as signal connection.

Published: 04-04-2013 14:22:02

lukelee.net