Wandering Thoughts archives


An operational explanation of Python metaclasses (part 3)

Following on from part 1 and part 2, the third thing that we can do with a metaclass is to create 'class-only attributes', attributes that are only visible on the class and not on instances of the class. In fact we can go further than just adding attributes; we can control what attributes are directly visible on the class without affecting what attributes are visible on instances of the class.

Simply adding attributes to the class (and only to the class) is done by putting attributes on the metaclass; in fact all attributes on the metaclass are visible on the class but not on instances of the class. Since this has some subtle bits, here is an example to illustrate:

class MiniMeta(type):
  one = "meta"
  two = "meta"

class Alpha(object):
  two = "alpha"

class Beta(Alpha):
  __metaclass__ = MiniMeta

bi = Beta()

Beta.one is "meta", but bi.one is an AttributeError; the attribute is visible only on the class and is not visible in instances. Beta.two and bi.two are both "alpha"; the attribute on the parent class overrides the attribute on the metaclass for both the class and an instance of the class (the same thing happens if we define two on Beta itself). Well, mostly.

The exception to parent classes taking priority is properties. A property set on the metaclass overrides anything else when you access it on the class, but is invisible to instances of the class. If you try hard this can be used to create attributes that have one value on the class and another value on instances of the class, which is sure to confuse everyone who reads your code unless you comment it heavily (and maybe even then).

The advanced version of this is that you can get partial or (almost) full control of attribute access to the class itself by setting up __getattr__, __getattribute__, __setattr__, and/or __delattr__ special methods on the metaclass. When people access attributes on the class itself (eg, as Beta.attr), these work just as if Beta was an ordinary instance of MiniMeta (because that's actually exactly what it is). However they are ignored when accessing attributes of Beta through instances of it, eg as bi.attr; the metaclass __getattribute__ and so on are not even called.

(As with properties, this can be abused to create attributes which have a different value on the class than on instances of the class.)

Note that this not-looking happens even when the lookup on the class is implicit, such as when you do len(bi) and Python looks to see if there's a Beta.__len__ method. In fact special method lookups don't go through any __getattribute__ or __getattr__ at all (this is covered in the official documentation).

(As with part 2, this is a specific example of a general metaclass power.)

Sidebar: method functions on the metaclass versus @classmethod

Both of these create 'class methods', method functions that take the class as the 'self' argument instead of an instance of the class. However they are not quite the same thing; in particular, @classmethod methods are visible (and work) from instances of the class while metaclass methods are not.

My personal opinion is that most of the time you want @classmethod because far more people are going to understand what you're doing. You want this even if there's no common ancestor class you can put the methods on and you have to invent some artificial mixin class to hold them.

python/UsingMetaclass03 written at 00:56:45; Add Comment

Page tools: See As Normal.
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.