metaclasses vs. inheritance

Jonathan Hogg jonathan at onegoodidea.com
Fri Jun 28 11:07:33 CEST 2002


On 28/6/2002 7:23, in article ghTS8.58749$mh.1826422 at news1.telusplanet.net,
"Ian McMeans" <imcmeans at home.com> wrote:

> Can someone give an example of a problem that is solved with metaclasses,
> which would be difficult or impossible to solve with inheritance?
> 
> I (very barely) understand metaclasses, but to me it just seems like
> inheritance, where the classes who get the benefit of the meta-class get the
> metaclass' features.

Interesting you should ask this as I've spent the last day reading up on
metaclasses and playing with them to better understand what can be done with
them. I find the best way to understand them is with a little graph:

           metaclass                 metaclass
              ^                         ^
              |                         |
           instance                  instance
              |                         |
            class B ---inheritance--> class A
              ^                         ^
              |                         |
           instance                  instance
              |                         |
              b                         a

This shows that metaclasses are "orthogonal" to normal inheritance. By which
I mean when you define a (new-style) class like so:

>>> __metaclass__ = type
>>> 
>>> class A: 
...     pass
... 

an instance of class 'A' has the type 'A':

>>> a = A()
>>> 
>>> a
<__main__.A object at 0x3f8cf0>
>>> type(a)
<class '__main__.A'>
>>>

But 'A' itself is an instance of the metaclass, in this instance 'type':

>>> A
<class '__main__.A'>
>>> type(A)
<type 'type'>
>>> 

Thus the metaclass is used in the construction and definition of classes. A
metaclass is a class itself (with the mind-bending result that the type of
'type' is 'type') and new ones can be defined using the same syntax. The
easiest way to define a new metaclass is to inherit from 'type' as that
defines all of the magic necessary to construct and manage classes.

The main reason one would want to define a new metaclass is to override some
of this magic in order to bend the way classes work. A useful method that
one might want to override in a metaclass is '__init__', which is called to
initialise a new class. An example will show what happens:

>>> class mytype( type ):
...    def __init__( cls, name, bases, dict ):
...        print '__init__'
...        print '    new class:', cls
...        print '    name:', name
...        print '    base classes:', bases
...        print '    dictionary:', dict
...        type.__init__( cls, name, bases, dict )
... 
>>> __metaclass__ = mytype
>>> 
>>> class Foo:
...     def foo( self ):
...         print 'foo'
... 
__init__
    new class: <class '__main__.Foo'>
    name: Foo
    base classes: ()
    dictionary: {'__module__': '__main__', 'foo': <function foo at
0x409768>}
>>> 

You can see from the above example that the 'mytype.__init__' is called
immediately after we finish defining the class 'Foo'. It is passed as
arguments the information used to initialise a new class, which is: the
class name, a tuple of the base classes (none in this example), and the
class dictionary which contains the method functions and class instance
variables.

Most people won't actually have any particular purpose for metaclasses, but
for altering the way that the language works they are invaluable. The
example I worked on yesterday is a mechanism for declaring methods and
classes final (in the Java sense) - prompted by an example someone else
wrote a while ago on this group. My metaclass intervenes in the creation of
new classes to check that final constraints aren't being broken:

>>> from final import *
>>> 
>>> class Foo:
...     def foo( self ):
...         print 'foo'
...     foo = finalmethod( foo )
... 
>>> class Bar( Foo ):
...     def foo( self ):
...         print 'bar'
... 
Traceback (most recent call last):
[..]
final.FinalError: finalmethod 'foo' of class 'Foo' may not be overriden
>>> 

and also:

>>> class Foo( finalclass ):
...     pass
... 
>>> class Bar( Foo ):
...     pass
... 
Traceback (most recent call last):
[...]
final.FinalError: finalclass 'Foo' may not be subclassed
>>> 

There's no easy way to do this sort of magic without the use of metaclasses.

Hope this helps a little. I'd better go do some real work ;-)

Jonathan




More information about the Python-list mailing list