GObject

GObject aus dem Python Modul "gobject" ist der objekt-orientierte Unterbau der in quasi allen GNOME-verwandten Projekten (Anwendungen, Programmbibliotheken, ...) Verwendung findet. Insbesondere im C-Umfeld ist GObject wichtig, hier ersetzt es die fehlende OOP-Funktionalität. Außerdem erlaubt es durch seinen beschreibenden Charakter einfach Hochsprachenbindings für bestehende Bibliotheken zu bauen weil die Attribute und die Vererbungshierarchie in C umgesetzter Klassen mittels GObject bereits definiert wird. Aber auch außerhalb der Programmiersprache C gibt es gute Gründe, GObject zu verwenden.

Mit GObject lässt sich angenehm trennen zwischen den Attributen eines Objektes, die zum Speichern objekt-spezifischer Daten verwendet werden (normale Attribute), und den Attributen, die von außen als Schnittstelle zu dem Objekt fungieren (mit GObject definierte Attribute). Außerdem erweitert die Basisklasse GObject ihre Erben um eine Signal-Funktionalität. Damit ist es Objekten möglich, einander zu informieren, wenn ein Ereignis eintritt. Eine Sonderform davon ist, dass ein anderes Objekt sich Bescheid geben lassen kann, wenn sich die (GObject-)Attribute eines anderen Objektes ändern.

Leider gibt es von "offizieller Seite" keine Dokumentation für die Python-GObject-Features. Die folgende Anleitung ist nach dem Prinzip "Try & Error" und unter Mithilfe des PyGTK-IRC-Channels entstanden und weist deshalb noch die ein oder anderen Lücken auf.

GObject verwenden

GObject lässt sich einfach als Basis für eigene Klassen verwerwenden indem man von ihm erbt:

import gobject

class MyClass(gobject.GObject):
...

Damit ist der Grundstein gelegt und die Verwendung der GObject-Features für Instanzen dieser Klasse möglich.

Möchte man die Klassendefinition noch etwas vollständiger machen, gibt man der Klasse mit dem Attribut "__gtype_name__" noch einen GObject-spezifischen Namen:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
...

Attribute

Attribute sind die Eigenschaften die Instanzen einer bestimmten Klasse besitzen. Die Klasse beschreibt, welche Attribute es gibt und kann definieren, welchen Datentyp dort hinterlegte Werte haben müssen, was der anfängliche Wert ist und ob ein Attribut nur lesbar oder auch schreibbar ist. Der eigendliche Wert des Attributs kann sich dann während der Laufzeit ändern und ist von Objekt zu Objekt unterschiedlich.

GObject-Attribute kann man über das Attribut "__gproperties__" definieren. Ihm weist man ein Dictionary zu dessen Keys die Namen der Attribute sind. Die Werte sind Tuples deren Elemente den Datentyp, den Namen, eine Beschreibung, den Anfangswert und den Modus beschreiben:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gproperties__ = {
'my-attribute': ( gobject.TYPE_BOOLEAN, 'my-attribute', 'this is my attribute', False, gobject.PARAM_READWRITE ),
}
...

Hier wird also ein Attribut mit dem Namen "my-attribute" vom Datentyp "Boolean" (True oder False) erzeugt. dessen Default-Wert False ist und das von außen sowohl gelesen alsauch geschrieben werden kann. Selbstverständlich kann man auch andere Datentypen und Zugriffsmodi verwenden. Eine halbwegs vollständige Liste findet sich hier: http://www.pygtk.org/docs/pygobject/gobject-constants.html

Um das Attribut auch verwenden zu können muss der Code noch etwas erweitert werden. Mit "gobject.PARAM_CONSTRUCT" wird GObject mitgeteilt, dass es das Attribut bei der Instanziierung eines Objektes auf den Default-Wert zu setzen hat. Außerdem wird in "__init__" ein Plätzchen für die tatsächlich gespeicherten Werte der Attribute in "_props" angelegt. Anschließend wird die GObject-Initialisierung durchgeführt. Die beiden Methoden "do_get_property" und "do_set_property" sorgen dafür, dass die Werte der Attribute später ihren Weg dorthin finden, wo sie gespeichert werden:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gproperties__ = {
'my-attribute': ( gobject.TYPE_BOOLEAN, 'my-attribute', 'this is my attribute', False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT ),
}

def __init__(self):
self._props = {}
gobject.GObject.__init__(self)

def do_get_property(self, pspec):
return self._props[pspec.name]

def do_set_property(self, pspec, value):
self._props[pspec.name] = value

Nun kann man auf dreierlei Wegen auf die Property zugreifen:

myobj = MyClass()
print myobj.get_property('my-attribute')
print myobj.get_properties('my-attribute')
print myobj.props.my_attribute

Zu beachten ist dabei, dass beim Zugriff über "props" aus dem '-' ein '_' wird, andernfalls bestünde Verwechlungsgefahr mit dem Minus-Zeichen.

Signale

Signale sind eine einfache Möglichkeit im Falle eines auftretenden Ereignisses andere Objekte darüber in Kenntnis zu setzen und so Programmabläufe anzustoßen. Ein Beispiel dafür ist ein Button der mit dem Signal "clicked" allen Objekten, die sich bei ihm für dieses Ereignis interessieren, mitteilt, dass das Event nun ausgelöst wurde. Die anderen Objekte können Funktionen mit diesen Ereignissen verknüpfen die aufgerufen werden, sobald die zugehörigen Ereignisse ausgelöst werden. Ein Programm könnte beispielsweise das aktuell geöffnete Dokument abspeichern sobald der Speichern-Button das "clicked" Signal auslöst.

Es ist auch möglich, für eigene Klassen Signale zu definieren und im entsprechenden Moment auszulösen. Die Verwaltung der Verknüpfungen zwischen den Ereignissen und den für ein Signal registrierten Funktionen (den sogenannten Callbacks - das Objekt ruft zurück, wenn etwas passiert ist) übernimmt GObject.

Jedes mögliche Signal wird ähnlich wie die Properties im Attribut "__gsignals__" angegeben. "__gsignals__" enthält ein Dictionary, dessen Namen die Namen der Signale sind. Der Wert ist erneut ein Tuple dessen Elemente das Signal genauer beschreiben. Der erste Wert beschreibt den Modus des Signals genauer - mehr konnte ich darüber noch nicht in Erfahrung bringen, für sachdienliche Hinweise bin ich jederzeit dankbar! Auch über die Funktionsweise des zweiten Wertes konnte ich bisher nur das folgende in Erfahrung bringen: Möchte man die weitere Abarbeitung eines Signals abbrechen können, indem man im Callback True zurück gibt, wählt man hier den Datentyp "Boolean" und verwendet für den ersten Wert "gobject.SIGNAL_RUN_LAST". In allen anderen Fällen steht hier "gobject.TYPE_NONE" und an erster Stelle "gobject.SIGNAL_RUN_FIRST". Das letzte Element ist ein Tuple das die Datentypen der dem Callback zusätzlich übergebenen Parameter beschreibt:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gsignals__ = {
'my-signal': ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ( gobject.TYPE_PYOBJECT, ) ),
}
...

Die Handler (Callbacks) die auf dieses Signal hin ausgeführt werden bekommen also das Objekt, dessen Signal ausgelöst wurde, als erstes und ein Python-Objekt als zweiten Parameter übergeben.

Ein Signal lässt sich mit der Methode "emit" auslösen. Ihr übergibt man den Namen des auszulösenden Signals und die weiteren Argumente die an die registrierten/verknüpften Callbacks übergeben werden sollen:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gsignals__ = {
'my-signal': ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ( gobject.TYPE_PYOBJECT, ) ),
}

def do_something(self):
self.emit('my-signal', object())

myobj = MyClass()
myobj.do_something()

Interessiert man sich für ein Signal eines Objekts und möchte eine Funktion ausführen, wenn das Signal ausgelöst wird, verknüpft man mit der Methode "connect" das Signal mit einer Funktion:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gsignals__ = {
'my-signal': ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ( gobject.TYPE_PYOBJECT, ) ),
}

def do_something(self):
self.emit('my-signal', object())

def my_callback(obj, *args, **kwargs):
print 'my_callback:', obj, args, kwargs

myobj = MyClass()
myobj.connect('my-signal', my_callback)
myobj.do_something()

Die Methode "connect" gibt eine ID zurück mit der man die Verknüpfung mittels "disconnect" später auch wieder loslösen kann:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gsignals__ = {
'my-signal': ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ( gobject.TYPE_PYOBJECT, ) ),
}

def do_something(self):
self.emit('my-signal', object())

def my_callback(obj, *args, **kwargs):
print 'my_callback:', obj, args, kwargs

myobj = MyClass()
hid = myobj.connect('my-signal', my_callback)
myobj.do_something()
myobj.disconnect(hid)
myobj.do_something()

Ein besonderes Schmankerl ist das Signal "notify". Es ist in GObject bereits mitgeliefert und wird immer emittiert wenn sich ein Attribut des zugehörigen Objektes ändert. Zudem wird für jedes Attribut das Signal "notify::attribut-name" emittiert wenn sich sein Wert ändert:

import gobject

class MyClass(gobject.GObject):
__gtype_name__ = 'GMyClass'
__gproperties__ = {
'my-attribute': ( gobject.TYPE_BOOLEAN, 'my-attribute', 'this is my attribute', False, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT ),
}

def __init__(self):
self._props = {}
gobject.GObject.__init__(self)

def do_get_property(self, pspec):
return self._props[pspec.name]

def do_set_property(self, pspec, value):
self._props[pspec.name] = value

def my_callback(obj, *args, **kwargs):
print 'my_callback:', obj, args, kwargs

myobj = MyClass()
myobj.connect('notify::my-attribute', my_callback)
print myobj.props.my_attribute
myobj.props.my_attribute = True
print myobj.props.my_attribute

Insbesondere diese Verknüpfung von Attributen und Signalen ist ein häufig gebrauchtes und von GObject mitgebrachtes Feature. Die Verwendung von GObject ist also allemal eine Überlegung wert.

Tags: Python