Metaclasses for fun and profit
Python since 1.4
Shiny new UI for Autolabel printers1
GTK 3, compatibility layer for GTK 2
Python wrapper that reads gi .typelib files to create Python classes
class Top(Window): title='Hello world' class Group(VBox): class Label(Label): label = 'Hello World!' class HiButton(Button): class Label(Label): label = 'Hi!' class ByeButton(Button): class Label(Label): label = 'Bye!'
class MyWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="Hello World") #\n self.box = Gtk.VBox() self.add(self.box) #\n self.label = Gtk.Label(label='Hello World!') self.box.add(self.label) #\n self.hibutton = Gtk.Button() self.hilabel = Gtk.Label(label='Hi!') self.hibutton.add(self.hilabel) self.box.add(self.hibutton) #\n self.byebutton = Gtk.Button() self.byelabel = Gtk.Label(label='Bye!') self.byebutton.add(self.byelabel) self.box.add(self.byebutton)
The class of the class
>>> class Foo(object): pass >>> type(Foo) <type 'type'> >>> class mBar(type): pass >>> class Bar(object): # class Bar(metaclass=mBar) >>> __metaclass__ = mBar >>> type(Bar) <class '__main__.mBar'>
The metaclass lets you customize the creation of the class, just as the class customizes the creation of objects.
Metametaclasses?
Python3 adds keyword arguments to class definition that are passed to the metaclass methods.
Metaclass methods available on class, but not its instances.
class mFoo(type): def foo(self, arg): pass class Foo(object): __metaclass__ = mFoo Foo.foo(42) # OK, self is Foo foo = Foo() foo.foo(42) # AttributeError
class Foo(object): __metaclass__ = mFoo # type if not specified foo = 42 def bar(self, arg): pass
class is just syntactic sugar for instantiating the metaclass!
def bar(self, arg): pass Foo = mFoo('Foo', (object,), { 'foo': 42, 'bar': bar })
class Top(Window): title='Hello world' class Group(VBox): class Label(Label): label = 'Hello World!' ################################################## class MyWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="Hello World") self.box = Gtk.VBox() self.add(self.box) self.label = Gtk.Label(label='Hello World!') self.box.add(self.label)
Figuring out attribute order (Python 2)
class mBaseWidget(type(GObject.GObject)): attributes = [] def __new__(cls, name, bases, namespace): ordered = [] objects = set(id(o) for o in namespace.itervalues()) for o in cls.attributes: if id(o) in objects: ordered.append(o) for o in ordered: cls.attributes.remove(o) # iter namespace['_attributes_ordered'] = ordered super(mBaseWidget, self).__new__(cls, name, bases, namespace)
class BaseWidget(GObjcet.GObject): __metaclass__ = mBaseWidget class Window(BaseWidget, Gtk.Window): pass class VBox(BaseWidget, Gtk.VBox): pass class Top(Window): class VBox(VBox): class ...: class VBox2(VBox): class ...: # Top._attributes_ordered == [ Top.VBox. Top.VBox2 ]
class Top(Window): title = 'Hello!' class Bar(FooWidget): foo = 42 # call FooWidget.foo.__set__ with 42 after __init__
Needs support from both metaclass and base class.
Can't use Property on metaclass, as it's not instantiated yet.
class mBaseWidget(type(GObject.GObject)): def __new__(...): ... defaults = namespace.setdefault('__defaults__', {}) for key, val in namespace.items() if hasattr(val, '__set__'): defaults[key] = None # overwrite any earlier default else: for b in bases: if (hasattr(getattr(b, key, None), '__set__') # setter or not key.startswith('_') and hasattr(getattr(b, 'props', None), key)): # gtk props defaults[key] = val del namespace[key]
class BaseWidget(GObject.GObject): def __init__(self, **kwargs): self.gtk_params = kwargs.pop('gtk_params', self.gtk_params) super(BaseWidget, self).__init__(**self.gtk_params) defaults = self.__defaults__ # set by metaclass for cls in reversed(self.__class__.__mro__): if hasattr(cls, '__defaults__'): defaults.update(cls.__defaults__) defaults.update(kwargs) for k, v in defaults.iteritems(): if hasattr(self.props, k): setattr(self.props, k, v) else: setattr(self, k, v)
The first hit is the one you're supposed to use.
super() uses __mro__ to find the proper base class.
Sometimes easier to overwrite, so here I walk it in reverse.
class Top(Window): class Group(VBox): class Title(Label): label = 'Hello' top = Top()
And then what? top.Group will still be the class
Iterate through the _attributes_ordered and instantiate. A job for the base class.
Widget __init__ may need to do additional things, such as packing subwidgets.
class BaseWidget(...): def __init__(...): ... objdict = {} for cls in self.__class__.__mro__: objdict.update(dict((v,k) for (k,v) in cls.__dict__.iteritems() for cls in reversed(self.__class__.__mro___): for o in getattr(cls, '_attributes_ordered', []): obj = o(parent_widget=self) k = objdict.get(o) if k: setattr(self, k, obj) self._attributes_ordered = ordered
Do stuff under the hood in the metaclass!
class mBaseWidget(...): def __init__(...): ... if len(bases) > 1: gtype = None for base in bases: if hasattr(base, '__gtype__'): # if more than one, and it is not the base GObject, fail gtype = base if gtype: ns2 = { '__doc__': 'Intermediate class' } base = super(mBaseWidget, cls).__new__(name, bases, ns2) bases = (base,)
# continues from last page gsignals = {}; knows_gsignals = {} for bcls in reversed(base.__mro__): gsignals.update(bcls.__gsignals__) if issubclass(bcls, GObject.GObject): known_gsignals.update(GSignal.signal_list_names(bcls) for sig in known_gsignals: del gsignals[sig] namespace['__gsignals__'] = gsignals # gproperties are handled similarly