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.
|
|