Hooks, aspect-oriented programming, and design by contract

Alex Martelli aleax at aleax.it
Fri Jan 25 18:47:14 EST 2002


Aahz Maruch wrote:
        ...
>>since the class-metaclass relationship is that of instance-class too.  A
>>metaclass _builds_ a class (in just the same sense as a class _builds_
>>an instance), defines the class's behavior (ditto).
> 
> Just to make sure I'm getting this straight, in the past James Althoff
> has talked about Smalltalk's design, and IIRC, he said that in Smalltalk
> it is *instances* of metaclasses that create classes.  Can anyone

In Smalltalk, metaclasses are instances of Metaclass.  Each metaclass
is the metaclass of a single class.

> confirm or deny?  If confirmed, am I understanding correctly that in
> Python, the metaclass directly instantiates a class object?

When a class statement is executed, Python (2.2) determines the
metatype (looks for a __metaclass__ attribute set in the class body,
else uses the metatype of the first base, if no bases uses global
module-level variable __metaclass__, else uses types.ClassType),
also known as metaclass.  Say the metaclass is M.

Then, Python calls M (arguments are class name string, bases, class 
dictionary as filled by the class statement's body).  This call returns
the object which Python then binds to the class name string in the
appropriate scope.

Here's a funny example:

def fakemeta(name, *args):
    class fake:
      def __init__(self, *args):
          if args: self.name = args[0]
          else: self.name=name
      def __call__(self): return self.name
    return fake()

class Funny:
  __metaclass__=fakemeta

print Funny
x=Funny()
print x

class Hilarious(Funny): pass

print Hilarious
y=Hilarious()
print y


So: metaclass can be ANY callable able to accept the needed
signature.  It can return ANY object -- that object will be "the
class" built and name-bound by the class statement (it needs
not be a class at all -- here it's an instance of a classic class).

Of course if it's not a class it had better mimic one decently --
this funny one only mimics "instantiability" (because that's just
the ability of being called...) but what it returns when you do
call it is a string.  It also mimics "subclassability", and plays
one more metaclass trick which matters in that case.

While Funny's class body binds __metaclass__ to fakemeta,
so a call to fakementa is used to build the object that is bound
to name Funny, the actual metaclass of Funny is not function
fakemeta -- it's nested class fake.  You find that out by
examining Funny.__class__.  So when you subclass Funny,
the metaclass used to build the subclass is nested class fake
(unless you set __metaclass__ explicitly in the subclass's
body), not function fakemeta.

So, function fakemeta is USED (when explicitly asked for)
as the metaclass, but then when all is said is done it's NOT
the metaclass... which is why it's a FAKE meta.

Nothing particularly useful here -- just trying to explore and
push some mental boundaries (mine, first of all:-).


> If I understand all this correctly, it would seem to me that Python's
> metaclasses bear (at least in theory) the same relationship to classes
> that classes have to instances, and therefore while changing the
> metaclass of a class may be possible, it would only have an effect on
> future usage of the class object.  There would be no effect on instances
> already created by the class object unless they make use of the class
> directly.

Yes, this makes sense.

> Have I gotten all this straight?

Seems so, yes.

> Mind you, I still have no clue what metaclasses are good for.  ;-)

They're only good for one job: creating classes and defining the
classes' behavior.  Just like classes are only good for the job of
creating instances and defining the instances' behavior.

You get a somewhat serviceable metaclass, the default one,
types.ClassType -- can't subclass it, only use it as is.  You get
another niftier metaclass, type, and I suspect many metaclasses
will subclass it.  Anyway: if you like the way type creates classes,
cool, use it as your metaclass and be happy.  If you want a class
created a bit differently, subclass type and override and tweak
__init__.  If you want a class that behaves a bit differently when
called, subclass type and override and tweak __call__.

Classic example might be "A class that keeps track of all of
its instances ever created (and incidentally keeps them all alive,
but you could remedy that with weakref)".  For this, you need
to tweak both class creation (adding an empty list) and class
calling, i.e. instance creation (appending to the list), right?

class nifty(type):
    def __init__(self, *args):
        type.__init__(self, *args)
        self._instances = []
    def __call__(self, *args, **kwds):
        result = type.__call__(self, *args, **kwds)
        self._instances.append(result)
        return result

__metaclass__ = nifty

class X:
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'X(%s)'%self.name

for c in 'ciao': X(c)

for x in X._instances: print x

Each instance of X is apparently created and immediately
thrown away, but actually kept in X._instances -- because
X's metaclass is nifty, so calling X runs nifty.__call__, which
does that.  Maybe this can be seen to have some potential
use and thus help see why one might want sometimes to
use different metaclasses than the two provided ones...?


Alex




More information about the Python-list mailing list