Slight metaclass confusion

Bengt Richter bokr at oz.net
Tue Sep 9 14:56:56 EDT 2003


On 9 Sep 2003 03:59:14 -0700, ben at transversal.com (ben at transversal.com) wrote:

>I am slightly confused about the way metaclasses work, having read
>"Metaclass Programming In Python, Parts 1 and 2"
>
>I get the fact that the instance of a metaclass is a class, but in
>this case I fail to see why the following does'nt work:
>
>>>> class Meta(type):
>...     def __str__(cls):
>...             print "I am " + repr(cls)
>... 
UIAM, you subclassed type, but you didn't override __new__, so ...
>>>> 
>>>> Class = Meta("Fish", (), {})
became essentially a pass-through call to type, i.e., effectively the same as
     Class = type("Fish",(),{})
and since you passed it an empty dict to make its Class.__dict__ from,

>>>> Class.__str__()
went looking in Class.__dict__ for '__str__' and couldn't find it. So it
went looking for it according to Class.mro(), which led to finding it in
the object base class -- i.e., object.__dict__['__str__'] succeeded. But what it
got was an unbound method, because neither of two things (accessing an ordinary
method as an attribute of an instance object, or accessing a class method as an attribute
of either and instance or its class) was happening to bind to the method. So you got a complaint.
Note that it's complaining about __str__ of object, not __str__ of your class Meta.

>Traceback (most recent call last):
>  File "<stdin>", line 1, in ?
>TypeError: descriptor '__str__' of 'object' object needs an argument
>
Note that your def __str__(cls) *does* exist in Meta.__dict__, but that wasn't the
dict used to construct Class.

>Whereas this does:
>
>>>> class Simple(object):
>...     def __str__(self):
>...             return "I am " + repr(self)
>...
In this case you are not overriding __new__ either, but the search for __new__ is finding
object.__new__, and that is making the object instance for you, of subtype Simple.

>>>> obj = Simple()
Actually, I think this amounts to Simple.__call__(), which must call cls.__new__() and go
down the mro chain to find object.__new__ and call it with cls (which would be Simple).

>>>> obj.__str__()
Looking for __str__ in this case is looking in type(obj).__dict__ first and finding it right there.
You are triggering it via attribute access, which dynamically creates the bound method that supplies
the self arg. Alternatively, type(obj).__str__ should be the unbound method,  to which you could pass
obj, and type(obj).__dict__['__str__'] should be the plain function that becomes a method by the
dynamic magic. It can be called with obj as arg like the unbound method.

>I am <__main__.Simple object at 0x402f676c>
>
>The tutorial does mention this (slightly), but doesn't make it clear
>why this is happening. I am probably missing something obvious though!
>
>Thanks for the help,
>
To get the effect you originally wanted, one way could be:

 >>> class Meta(type):
 ...     def __str__(cls): return 'I am '+ repr(cls)
 ...     __str__ = classmethod(__str__)
 ...     def __new__(cls, name): return type.__new__(cls, name, (),cls.__dict__.copy())
 ...
 >>> Class = Meta('Fish')
 >>> Class
 <class '__main__.Fish'>
 >>> Class.__str__()
 "I am <class '__main__.Fish'>"

Interestingly,

 >>> str(Class)
 "I am <class '__main__.Meta'>"

apparently the str builtin doing str(x) goes after type(x).__str__, e.g.,

 >>> Class.__str__
 <bound method Meta.__str__ of <class '__main__.Fish'>>
 >>> Class.__str__()
 "I am <class '__main__.Fish'>"

vs

 >>> type(Class).__str__
 <bound method type.__str__ of <class '__main__.Meta'>>
 >>> type(Class).__str__()
 "I am <class '__main__.Meta'>"


The above is my current view of how things work. I think/hope it's pretty close, but I am not
speaking from a code walk here, so there may be some details awry. Corrections welcome.

BTW, the __metaclass__ class variable and how that works is something that belongs in
a discussion of Python metaclass programming, but someone else can fill that in ;-)

HTH and does not mislead.

Regards,
Bengt Richter




More information about the Python-list mailing list