Sub-classing GObject in Python

Or how to create custom properties and signals with PyGTK

Revision History
Revision 0.1.1 29-10-2003

Added gobject.SIGNAL_ACTION for key bindings stuff

Revision 0.1.0 05-10-2003
  • Lots of typos fixed

  • New section: 'Overriding the class closure'

  • Lots of changes after the great review of Christian Reis

Revision First Draft 29-09-2003

Table of Contents

Introduction
Advantages of subclassing GObject
Creating custom properties
Using the properties
Some warnings when using GObject properties in Python
Properties with long names
Subclassing other GObject derivates
Creating your own signals
Using your signals
Overriding the class closure
Bibliography

Abstract

This document tries to explain the process of creating subclasses of GObject, the base class of the GNOME framework, in Python.

GNOME (GNU Network Object Model Environment) is a project whose main goals are to create a complete, free and easy-to-use desktop environment for users as well as a powerful application development framework for software developers. As a result of this second goal GNOME is based on a set of libraries which are very easy to access from a large amount of programming languages.

The library most modules depend on is GLib which provides a lot of useful functionality embedded and used in the GNOME framework; this document in particular discussed the Object system defined in Glib that allow us to use Object Orientation throughout the GNOME framework. The Glib library, as most of the GNOME core libraries, is programmed in the C programming language, which is not an Object Oriented language in the sense that it does not contains the syntax and built-in types that are conventionally used when programming in an Object Oriented way in other languages like C++, Java or Python. But this does not mean that you can not program in an Object Oriented way with C, it's only that you have to do some extra work. And it's your lucky day, because GLib does most of this work for you. GObject is commonly known as the part of GLib that provides the Object Oriented features that C lacks.

One of the nice things that GObject provides is a class mechanism with the kind of things you are used to work within an Object Oriented language like inheritance, polymorph-ism, interfaces and virtual methods. But it also gives you a very powerful feature that allows you to connect different objects in an asynchronous and independent way. Yes I'm talking about signals. If all this is not enough for you, then let me tell you that GObject also support introspection allowing some programs (like GUI builders or debuggers) to know the properties of an object.

But what if you are used to another programming language like Python and you create GNOME applications using some of the GNOME bindings (like PyGTK) but you still want to use some of those nice features of GObject? Well, in that case, this article is for you.

You may wonder why not use Python's own Object Oriented features to implement Object Oriented programs. And in most of the situations you are probably right. But, as said before, there are a few features of GObject, that are really useful when working with the GNOME framework. These are:

One more good thing about GObject subclassing is that you can always use the normal Python Object Oriented features at the same time you use the GObject features and things will just work smoothly.

So let's get some action and create our first example. We are going to create a Car class with a fuel property that indicates the amount of fuel that our car has. This is the code

  1  import pygtk 1
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  class Car(gobject.GObject):
  6      __gproperties__ = { 2
  7           'fuel' : (gobject.TYPE_FLOAT,                        # type
  8                     'fuel of the car',                         # nick name
  9                     'amount of fuel that remains in the tank', # description
 10                     0,                                         # minimum value
 11                     60,                                        # maximum value
 12                     50,                                        # default value
 13                     gobject.PARAM_READWRITE)                   # flags
 14      }
 15
 16      def __init__(self):
 17           gobject.GObject.__init__(self) 3
 18           self.fuel = 50
 19
 20      def do_get_property(self, property): 4
 21           if property.name == 'fuel':
 22               return self.fuel
 23           else:
 24               raise AttributeError, 'unknown property %s' % property.name
 25
 26      def do_set_property(self, property, value): 5
 27           if property.name == 'fuel':
 28               self.fuel = value
 29           else:
 30               raise AttributeError, 'unknown property %s' % property.name
 31
 32  gobject.type_register(Car) 6
1

These are the standard imports you need to use when using PyGTK

2

The __gproperties__ dictionary is a class property where you define the properties of your cars. In our example we just have one property, the fuel. The format to define a property is the following:

  • The key is the name of the property, in our case, fuel.

  • The value is a tuple which describe the property. The number of elements of this tuple depends on its first element but the tuple will always contain at least the following items:

    • The first element is the property's type. This is the list of the possible types: TYPE_BOOLEAN, TYPE_BOXED, TYPE_CHAR, TYPE_DOUBLE, TYPE_ENUM, TYPE_FLAGS, TYPE_FLOAT, TYPE_INT, TYPE_INT64, TYPE_INTERFACE, TYPE_INVALID, TYPE_LONG, TYPE_NONE, TYPE_OBJECT, TYPE_PARAM, TYPE_POINTER, TYPE_PYOBJECT, TYPE_STRING, TYPE_UCHAR, TYPE_UINT, TYPE_UINT64, TYPE_ULONG. If you don't find the type you are looking for, you probably want the TYPE_PYOBJECT type.

    • The second element is the property's nick name, which is a string with a short description of the property. This is generally used by programs with strong introspection capabilities, like the graphical user interface builder Glade.

    • The third one is the property's description or blurb, which is another string with a longer description of the property. Also used by Glade and similar programs.

    • The last one (which is not necessarily the forth one as we will see later) is the property's flags, which is a bitwise or'ed combination of the following parameter flags:

      • gobject.PARAM_CONSTRUCT: The property will be set upon object construction.

      • gobject.PARAM_CONSTRUCT_ONLY: The property will only be set upon object construction.

      • gobject.PARAM_LAX_VALIDATION: Upon property conversion strict validation is not required. In C if your property has the TYPE_FLOAT type and you try to set it with 10, GObject internally calls g_param_value_convert() to get a float from that constant unless this flag is used.

      • gobject.PARAM_READABLE: The property is readable.

      • gobject.PARAM_WRITABLE: The property is writable.

      • gobject.PARAM_READWRITE: The property is readable and writable. This is the same as gobject.PARAM_READABLE | gobject.PARAM_WRITABLE

      Note that setting PARAM_CONSTRUCT* without PARAM_WRITABLE will fail.

      Actually, these flags are not working very well in PyGTK and I have run into some problems while working with them:

      • Setting the flag gobject.PARAM_READABLE still let you call the set_property() method See the bug number 121544.

      • The gobject.PARAM_CONSTRUCT method does make GObject call your do_set_property() method upon object initialization but in a different object instance!! See the bug number 123891.

  • The absolute length of the tuple depends on the property type (the first element of the tuple). Thus we have the following situations:

    • If the type is TYPE_BOOLEAN, TYPE_ENUM, TYPE_FLAGS or TYPE_STRING, the forth element is the default value of the property.

    • If the type is TYPE_*CHAR, TYPE_*INT*,TYPE_*LONG, TYPE_FLOAT or TYPE_DOUBLE, the forth element is the minimum accepted value, the fifth element is the maximum accepted value and the sixth element is the default value. This is the case of our example.

    • If the type is TYPE_PARAM, TYPE_BOXED, TYPE_POINTER or TYPE_OBJECT, there are no additional elements.

3

Next thing you need to do is call the __init__ method of gobject.GObject. This call is used to register your properties and signals. You also need to create the appropriate Python instance members to hold your properties. In this case we just need a fuel instance variable.

4

When using GObject properties you need to define a method called do_get_property which will be called for you each time somebody tries to access one of your properties. All you need to do is return the appropriate property.

5

You also need to define the do_set_property method, that will be called when somebody tries to set one of your property's value. GObject will make sure that the type of the new value matches the type of your property and that the value is in the appropriate range if this is needed (for char, int, long, float and double types) before calling this method.

6

Last thing you have to do is a call to gobject.type_register with your class as the argument. This call registers your class as an official GType and it is absolutely necessary. Some people say that this is not a very elegant solution but so far that's the way to do it. The main problem is that this is a class initialization process and not an instance initialization issue. That's why it can not be implemented in the __init__() method. Maybe using Python metaclasses can solve this in a nice manner.

You can use your brand new custom properties as with any other regular property like the ones you find using GTK+ Widgets. For those of you that don't know what I'm talking about, here is a small example:

>>> from car1 import Car
>>> aCar = Car()
>>> print "The car has %f of fuel at the beginning" % aCar.get_property('fuel')
The car has 50.000000 of fuel at the beginning
>>> aCar.set_property('fuel', 20)
>>> print "Now the car has %f of fuel" % aCar.get_property('fuel')
Now the car has 20.000000 of fuel

What I think is really useful is connecting a callback to the notify signal of a property. In the next example we will create a function that will check if we are running out of fuel using the notify mechanism of GObject:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  from car1 import Car
  6
  7  def myCallback(obj, property):
  8      if property.name == 'fuel':
  9          if obj.get_property('fuel') < 10:
 10              print 'we are running out of fuel!!'
 11
 12  def test():
 13      aCar = Car()
 14      aCar.connect('notify', myCallback)
 15      aCar.set_property('fuel', 5.0)
 16
 17  if __name__ == '__main__':
 18      test()
 19

In this example, the myCallback function is called for any change in any property of aCar. Suppose we have 4 different properties in a Car, that's why we need the if clause at the beginning of the callback.

Now we will see in another way of doing the same as in the previous example but using a nice feature of GObject signal names:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  from car1 import Car
  6
  7  def myCallback(obj, property):
  8      if obj.get_property('fuel') < 10: 1
  9          print 'we are running out of fuel!!'
 10
 11  def test():
 12      aCar = Car()
 13      aCar.connect('notify::fuel', myCallback) 2
 14      aCar.set_property('fuel', 5.0)
 15
 16  if __name__ == '__main__':
 17      test()
2

Here we are connecting myCallback to the notify signal of the object aCar but only when the property changed is fuel.

1

That's why we don't need to check if the property changed is the fuel property.

When naming a property with more than one word you have to be careful with the character used to separate the words. GObject translates all the underscore characters to hyphen characters so if you have a property called background_color, its internal and valid name will be background-color. The places where you have to be careful about this are the do_get_property() and do_set_property() methods and the signal connection calls. Let's see an example:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  class Style(gobject.GObject):
  6      __gproperties__ = { 1
  7          'foreground_color' : (gobject.TYPE_STRING, 'foreground color',
  8                    'string that represents the foreground color',
  9                    'black', gobject.PARAM_READWRITE),
 10          'background_color' : (gobject.TYPE_STRING, 'background color',
 11                    'string that represents the background color',
 12                    'white', gobject.PARAM_READWRITE),
 13          }
 14
 15      def __init__(self):
 16          gobject.GObject.__init__(self)
 17          self.foreground_color = 'black' 2
 18          self.background_color = 'white'
 19
 20      def do_get_property(self, property):
 21          if property.name == 'foreground-color': 3
 22              return self.foreground_color
 23          elif property.name == 'background-color':
 24              return self.background_color
 25          else:
 26              raise AttributeError, 'unknown property %s' % property.name
 27
 28      def do_set_property(self, property, value):
 29          if property.name == 'foreground-color':
 30              self.foreground_color = value
 31          elif property.name == 'background-color':
 32              self.background_color = value
 33          else:
 34              raise AttributeError, 'unknown property %s' % property.name
 35
 36  gobject.type_register(Style)
1

See how we name our properties: we use underscores to separate the words.

2

Note that in Python foreground-color is not a valid name so we have to use foreground_color.

3

Here we see how GObject has transformed our names to its internal names, which use hyphens instead of underscores.

Note that you can use long names like backgroundColor (this means capitalizing the first letter of every word but the first one) to avoid this problem.

The other thing you probably want to use when subclassing GObject is define custom signals. You can create your own signals that can be emitted so users of your class can connect to them.

When a signal is emitted a set of closures will be executed. A closure is an abstraction of the callback concept. A closure is the callback itself (a function pointer), the user data (it will be the last parameter to the callback) and another function for cleanup issues, which will not be discussed in this document.

For the sake of this article you don't really need to know the difference between a callback and a closure so both terms will be used. But be advised that this is not totally correct.

As we said before, when a signal is emitted, a set of closures will be executed. One of them is the same one for all the instances of this class and hence its name: the class closure, and the other ones are custom user callbacks. Note that not all the signals need to have a class closure because it is optional.

The GObject signal system is a very flexible (and complex) one so you can change the order in which your callback is executed very easily. Let's see the states in which a signal goes so you can understand what you can do:

There are other states like EMISSION_HOOK and RUN_CLEANUP which you can't use from Python so they won't be explained here.

I think this is enough theory for now so let's jump into some real code. We can use the signal concept to improve our car class. We can have an engine-started signal that will be emitted when the car's engine is started so other parts of the car can connect to this signal and do something useful.

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  class Car(gobject.GObject):
  6      __gproperties__ = {
  7          'fuel' : (gobject.TYPE_FLOAT, 'fuel of the car',
  8                    'amount of fuel that remains in the tank',
  9                    0, 60, 50, gobject.PARAM_READWRITE)
 10          }
 11
 12      __gsignals__ = { 1
 13          'engine-started' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
 14                              (gobject.TYPE_FLOAT,))
 15          }
 16
 17      def __init__(self):
 18          gobject.GObject.__init__(self)
 19          self.fuel = 50
 20
 21      def do_get_property(self, property):
 22          if property.name == 'fuel':
 23              return self.fuel
 24          else:
 25              raise AttributeError, 'unknown property %s' % property.name
 26
 27      def do_set_property(self, property, value):
 28          if property.name == 'fuel':
 29              self.fuel = value
 30          else:
 31              raise AttributeError, 'unknown property %s' % property.name
 32
 33      def do_engine_started(self, remaining_fuel): 2
 34          print 'The engine is ready and we have still %f of fuel' % self.fuel
 35
 36      def start(self): 3
 37          self.emit('engine-started', self.get_property('fuel'))
 38
 39  gobject.type_register(Car)
1

The __gsignals__ dictionary is a class property where you define the signals of your car. In our example we just have one signal, the engine-started. The format to define a signal is the following:

  • The key is the name of the signal, in our case, engine-started

  • The value is a tuple which describes the signal and has the following elements:

    • The first one is the signal's flags, which defines when the class closure of this signal will be invoked. It can have the following values:

      • gobject.SIGNAL_RUN_FIRST

      • gobject.SIGNAL_RUN_LAST

      See the signal's states above to understand these constants. Note that no matter there are four different signal states you can only set two different flags here since these are the only states that affect the class closure, which is what we are defining here. The other two states are meaningful when working with user defined callbacks

      Note that usually gobject.SIGNAL_RUN_LAST is more flexible since it allows the users of your class to connect callbacks that will be executed before or after the class closure is executed. If you use gobject.SIGNAL_RUN_FIRST your users will only be able to connect callbacks that will be executed after the class closure is executed.

      There is another flag called gobject.SIGNAL_ACTION which is useful is you are trying to use key bindings with your signal. For example if you want that everytime the user press a key your signal is emitted, you need to add this flag to the signal definition.

    • The second one is the type of the return value of the signal. In our case it's gobject.TYPE_NONE since the signal does return nothing.

    • The third and last one is a tuple which indicates the type of each parameter of the signal. In our case we have a single parameter for the signal which is of type float. As you can guess, our signal will have the fuel property as the first parameter.

2

This method is optional but if it exists it will be executed when the class closure for our signal is executed. It will be invoked each time the engine-started signal is emitted and in the order we defined with the gobject.SIGNAL_RUN_LAST parameter. The name of this method starts with do_ and then the name of the signal, with hyphens (which are illegal in Python functions) converted to underscore characters

3

Here we provide a custom method to start the engine. It emits the signal engine-started with the fuel property as its only parameter

Now that we have our own signal we can connect callbacks to it to make it a little bit more useful:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  from car4 import Car
  6
  7  def myCallback(obj, remaining_fuel, data=None): 1
  8      print '***** Beginning of User callback *****'
  9      print 'The engine is starting and we still have %f of fuel' % remaining_fuel
 10      print '***** End of User callback *****'
 11
 12  def lastCallback(obj, remaining_fuel, data=None): 2
 13      print '***** Callback connected with connect_after *****'
 14      obj.set_property('fuel', remaining_fuel - 10)
 15      print 'Now we have %f of fuel' % obj.get_property('fuel')
 16      print '***** End of this callback *****'
 17
 18  def test():
 19      aCar = Car()
 20      aCar.connect('engine-started', myCallback) 3
 21      aCar.connect_after('engine-started', lastCallback)
 22
 23      aCar.start()
 24
 25  if __name__ == '__main__':
 26      test()
1

This first callback will be invoked before the class closure because our signal was created with the RUN_LAST flag.

2

This callback will be invoked after the class closure because it will be connected with the connect_after method.

3

These are the methods that we use to connect our callbacks to the signal.

Now that you know what the class closure is, you may want to override it. Suppose you want to subclass the Car class with a LuxuryCar class and you want to do something more when the engine is started. In this particular case when you subclass a Python class with another Python class there is nothing special with this. Let's see an example:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gobject
  4
  5  from car4 import Car
  6
  7  class LuxuryCar(Car):
  8      def do_engine_started(self, remaining_fuel):
  9          Car.do_engine_started(self, remaining_fuel)
 10          print 'Welcome to the Luxury Car'
 11
 12
 13  if __name__ == '__main__':
 14      luxuryCar = LuxuryCar()
 15      luxuryCar.start()

Note that since you are not defining new properties or signals you don't have to call gobject.type_register(LuxuryCar)

But, what if you want to override the class closure of a GTK+ Widget? The GTK+ library is written in C and most of the functions that implement the class closure for the signals of the widgets are not public. This among other consequences means that they are not accessible to the PyGTK bindings.

Now suppose you want to create a special kind of gtk.Entry that will not allow the user to paste anything into it. In the regular gtk.Entry the class closure of the 'paste_clipboard' signal has the function that handles the paste behavior. So our class need to override this class closure. This is the code you need to write:

  1  import pygtk
  2  pygtk.require('2.0')
  3  import gtk
  4  import gobject
  5
  6  class EvilEntry(gtk.Entry):
  7      __gsignals__ = {
  8          'paste_clipboard' : 'override' 1
  9          }
 10
 11      def __init__(self):
 12          gobject.GObject.__init__(self)
 13
 14      def do_paste_clipboard(self): 2
 15          print "You tried to paste something but you can't!! muHAHAHA"
 16
 17  gobject.type_register(EvilEntry)
 18
 19  if __name__ == '__main__':
 20      win = gtk.Window()
 21      win.connect('destroy', lambda e: gtk.main_quit())
 22
 23      entry = EvilEntry()
 24      entry.show()
 25      win.add(entry)
 26      win.show()
 27
 28      gtk.main()
1

This time the value of this signal entry is the string override. This means we are going to override the class closure of the 'paste_clipboard' signal

2

This will be the method that will be executed by the new class closure. Its name is composed by do_ plus the signal name.

Note that in this particular case you could have done the same thing in another way: connect a callback to the 'paste_clipboard' signal and call stop_emit_by_name in this callback. But you can do it only because the 'paste_clipboard' signal is defined as a RUN_LAST signal allowing the user callback code to be executed before the class closure. What if the signal is defined as RUN_FIRST? Then, there is no way to modify the default behavior unless you override the class closure.

I know it's hard to find a good use of overriding the class closure but you can find several ones of it looking in the DiaCanvas library. This library is build in top of GnomeCanvas and it's very useful if you want to draw diagrams. There is a class called PlacementTool which is used when the user clicks on the canvas and he/she is creating new items. You can override the class closure of the 'button-press' and 'button-release' signals if you want to do special things when creating items (as I needed to do some time ago :)

Mathieu Lacage. The Glib Object system v0.8.0. (FIXME: dead link 2009-09-17)

The GTK+ guys. GObject Reference Manual.

James M. Cape. GTcpSocket Library Manual. (FIXME: dead link 2009-09-17)