Slight metaclass confusion

Bengt Richter bokr at oz.net
Tue Sep 9 22:08:22 CEST 2003


On 9 Sep 2003 18:56:56 GMT, bokr at oz.net (Bengt Richter) wrote:

>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 ...
ARGH. I was was partly mistaken, obviously.
>>>>> 
>>>>> Class = Meta("Fish", (), {})
>became essentially a pass-through call to type, i.e., effectively the same as
bzzt. wrong ;-/ It would (I think) have called type.__new__(Meta, 'Fish', (), {})

>     Class = type("Fish",(),{})
>and since you passed it an empty dict to make its Class.__dict__ from,
>
The empty dict did become the Class.__dict__ initial value though, I believe.

>>>>> Class.__str__()
>went looking in Class.__dict__ for '__str__' and couldn't find it. So it
still true
>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.
True, but maybe misleading.
>
>>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).
>
I need to read code for a while, and not write stuff I have to qualify...

>>>>> 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.
>
I see David Mertz's post now. I learned that going off the end of the mro search for foo
then goes on to the metaclass, which presumably means that __str__ would have been found
if object.__str__ hadn't snagged it first in the mro chain.

>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.
Sorry about the pass-through-to-type mislead. That was plain wrong, along with the unstated
implication that Meta.__str__ was plain unreachable from the resulting Class. At least I qualified
it with UIAM ;-/

Whoa, I'm late. gotta run...

Regards,
Bengt Richter




More information about the Python-list mailing list