Simplified GObject properties

GNOME, PyGTK, Python 4 Comments

Today I spent most of the day to finish off an implementation of bug 338098, adding a property helper to the python gobject bindings.

The support for defining your own GObject properties in PyGObject/PyGTK has always been a nail in my eye:

  • You need to create a dictionary filled with crazy options
  • You need to implement get_property and set_property to store/retrieve the values for each property.
  • There’s very little help if you do a mistake, a cryptic error message will be displayed which gives you insufficient clues to figure out what you’ve done wrong.

A long time ago I solved this in kiwi. But I never quite got around to submit these helpers to pygobject where they belong.

With the new property helper it gets much simpler. Let’s say you want to create a new object called Color which has the three integer properties called red, green and blue:

  class Color(gobject.GObject):
    red = gobject.property(type=int)
    green = gobject.property(type=int)
    blue = gobject.property(type=int)

That’s it!
Easy to read and understand.

If you want to set a property, just modify the properties as if they were a normal python attribute:

  color = Color()
  color.red = 10
  color.green = 20
  color.blue = 300

Same goes for access:

  >>> color.green
  20

Now, suppose you want a read-only property, then you can use the “new” python 2.4 decorator syntax:

  @gobject.property
  def fnorb(self):
     return 'fnuffra'

Which will add a fnorb property which will always return the string ‘fnuffra’ and raise an exception if your try to modify it.

The patch has not yet landed in pygobject’s SVN repository, but it’ll probably go in shortly, assuming nobody have any objections.

Once this is done I intend to work on a similar solution for signals.

Metaclasses & PyGObject

GNOME, PyGTK, Python Comments Off

I recently ran into a long-standing bug in the Kiwi plugin for Gazpacho.

I have this class in Kiwi: (simplified for explanation purposes)

class KiwiEntry(gtk.Entry):
  gproperty('data_type', object)
  def __init__(self, data_type):
     gtk.Entry.__init__(self)
     self.set_property('data_type', data_type)

This worked absolutely fine when you construct the object by calling the constructor from your python code.
However, Gazpacho and also Glade-3 uses gobject.new() to construct objects, which uses an alternative construction path.

When using gobject.new() to construct the KiwiEntry, an exception would be raised that said that data_type was not a property of the object!

After a few hours of digging in Gazpacho and Kiwi I found out that the underlying bug was actually in PyGObject, the python bindings for GObject. It turned out that the GObject subclasses constructed using gobject.new was not properly created during the constructor phase.

Changing the code to set the property immediately after the constructor would magically solve the bug.

First I tried to solve the bug by using gobject.idle_add. It would actually work but it did not really satisfy me since it depends on the mainloop running. Not too much of a deal actually, since you need a mainloop to use the entry subclass anyway. However, I knew that this would cause hard to debug problems in the future.

The solution of course is to use a metaclass!
A metaclass is a class of a class, which allows you to modify how a class behaves. For instance, you can add a piece of code which is ran when the class is constructed if you like. Or in my case, add a hook which is called after the normal construct has been called.

class MyMeta(gobject.GObjectMeta):
    def __call__(self, *args, **kwargs):
        instance = super(MyMeta, self).__call__(*args, **kwargs)
        instance.__post_init__()
        return instance

This metaclass overrides the __call__ method which is the code being run when you put two parenthesis after the class:

  class()

To use this metaclass for an object, just define the __metaclass__ class attribute in the subclass(es) you wish to use it in:

class KiwiEntry(gtk.Entry):
  __metaclass__ = MyMeta

  def __init__(self, data_type=None):
     gtk.Entry.__init__(self)
     self._data_type = data_type

  def __post_init__(self):
     self.set_property('data-type', self._data_type)

A Metaclass is a very powerful concept, it should be used with caution. It’s very easy to abuse them and make code not only hard to understand, but almost impossible to debug.

For more information about metaclasses, refer to the Unifying types and classes in Python 2.2 which contains a quite technical explanation.