Thursday, June 10, 2010

QSignalMapper - at your service

The QSignalMapper class is very useful, for example, in situations were you have a couple of QPushButtons and you want to connect the clicked-signal of each button to a single slot and still be able to identify the sender. It's possible to find out which object emitted a signal by calling sender() inside the slot, but I don't think that's good practice because you introduce a strong coupling between the signaling object and the slot.

A better solution is to use the QSignalMapper class which re-emits the signal with an additional configurable parameter. The parameter can be one of the following types: int, QString, QObject or QWidget.

So without further ado, here's a self describing example:
#
# Example showing how QSignalMapper can be used to manage an arbitrary
# numbers of parameterless signals and re-emit them with an argument
# identifying the sender.
#
# Each signal is associated in the QSignalMapper with either an int,
# QString, QObject or a QWidget which is passed as argument to the slot
# connected to the QSignalMapper.
#
from PyQt4.QtCore import QSignalMapper, pyqtSignal
from PyQt4.QtGui import QApplication, QFrame, QGridLayout, QPushButton

class Grid(QFrame):
    """ Lay out widgets in a grid. """

    clicked =  pyqtSignal(int)
    """
    This signal will be emitted when on one of the grid items is clicked.
    The grid item's index will be passed as argument.
    """

    def __init__(self, items, colCount, parent=None):
        """
        Create Grid and setup QSignalMapper.

        Connect each item's 'clicked' signal and use the sequence index
        as map value which will be passed as argument when the Grid's
        clicked signal is emitted.

        items: sequence with widgets having a 'void clicked()' signal
        colCount: column count for each row
        parent: parent widget, default None
        """
        super(Grid, self).__init__(parent)

        # Create a grid layout.
        layout = QGridLayout()
        self.setLayout(layout)

        # Create the signal mapper.
        signalMapper = QSignalMapper(self)

        for cnt, item in enumerate(items):
            # Setup mapping for the item. In this case, the
            # mapping is the sequence index of the item.
            signalMapper.setMapping(item, cnt)

            # Connect the item's 'clicked' signal to the signal
            # mapper's 'map' slot.
            # The 'map' slot will emit the 'mapped' signal
            # when invoked and the mapping set previously will be
            # passed as an argument to the slot/signal that is
            # connected to the signal mapper's 'mapped' signal.
            item.clicked.connect(signalMapper.map)

            # Add the widget to the grid layout
            layout.addWidget(item, cnt / colCount, cnt % colCount)

        # Forward the signal mapper's 'mapped' signal via the Grid's
        # 'clicked' signal. This will handle all widgets' 'clicked'
        # ssignals.
        signalMapper.mapped.connect(self.clicked)

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)

    # Create grid items
    items = (QPushButton('Open'), QPushButton('Close'),
             QPushButton('Read'), QPushButton('Write'),
             QPushButton('Delete'))

    win = Grid(items, 2)

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

    win.show()
    sys.exit(app.exec_())
Hope this was to any help.

Wednesday, May 26, 2010

ABCs - Abstract Base Classes

What's ABCs?
The abc (abstract base class) module was introduced in version 2.6 of Python. The module provides the infrastructure for defining an abstract base class and can typically be used in situations where frameworks need to specify a bunch of interfaces to be implemented to ensure the functionality. An example can be a database application where the framework requires you to provide a Data Access Object with basic CRUD operations and therefore defines an interface, call it UserDao, with the following methods:

UserDao

  • getById - Return a user entity with the specified id
  • store - store a user entity
  • remove - remove a user entity from the persistent storage
To ensure that developers follows the protocol an abc is introduced.

The UserDao abc
The following example will show you basic usage of the abc module. You should read the module documentation for more details.

Let's create the UserDao abc:
from abc import ABCMeta, abstractmethod

class UserDao:
    """
    This interface should be implemented to provide the framework with
    basic user CRUD operations.
    """
    __metaclass__ = ABCMeta

    @abstractmethod
    def getById(self, userId):
        """ Return the user with the user id. """

    @abstractmethod
    def store(self, user):
        """ Persist the user. """

    @abstractmethod
    def remove(self, user):
        """ Remove the user from the persistent storage. """
If we try to create an instance of this class we get the following error:
>>> dao = UserDao()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: Can't instantiate abstract class UserDao with abstract methods getById, remove, store
In other words, we have a well defined interface to support CRUD operations for User entities and we also have an easy to read documentation of what's expected from a UserDao. If we would rely on duck typing instead of defining the abc, we would need to document the expected behavior for the UserDao elsewhere. I think that the biggest advantage of using ABCs is that you have the interface definition and documentation in one place.

Just to prevent flaming :) I enjoy duck typing and use it a lot but I think that ABCs are useful is some situations.

Implementing the abc
Implementing the UserDao can be done by sub-classing the UserDao.
class User(object):
    def __init__(self):
        self.id = None
        self.name = None

class UserDaoImpl(UserDao):

    def getById(self, userId):
        user = User()
        user.id = userId
        user.name = 'A User'
        return user

    def store(self, user):
        print "Storing: ", user.id, "-", user.name

    def remove(self, user):
        print "Removing: ", user.id
And to complete the example:
userDao = UserDaoImpl()
user = userDao.getById(10)
user.name = "New Name"
userDao.store(user)
Wrap up
I haven't investigated if ABCs introduces any performance overhead but I can't really see why they should. One advantage of using ABCs in frameworks is that you can have the documentation and interface details as a simple Python class. Another advantage is that Python will issue an error if a sub-class doesn't implement all abstract methods/properties of the abc, which is probably the author's intention.

Tuesday, May 4, 2010

Network manager problems on Kubuntu 10.04

I installed 10.04 as soon as it was released and it worked right out of the box. But suddenly today the network manager refused to start.

Well, I could see the icon in the systray but it didn't show any network interfaces (I have eth0 and wlan0).

To get me an IP I had to run dhclient manually from the terminal which was successful. Finally I could google for help.

Google pointed me to this blog post where the author had run into the same problem.

Apparently there is a bug filed for this issue, I'm just too lazy to search in the bug database :)

Just wanted to share this with you if you bump into it.