views:

186

answers:

3

I was wondering what happens to methods declared on a metaclass. I expected that if you declare a method on a metaclass, it will end up being a classmethod, however, the behavior is different. Example

>>> class A(object):
...     @classmethod
...     def foo(cls):
...         print "foo"
... 
>>> a=A()
>>> a.foo()
foo
>>> A.foo()
foo

However, if I try to define a metaclass and give it a method foo, it seems to work the same for the class, not for the instance.

>>> class Meta(type): 
...     def foo(self): 
...         print "foo"
... 
>>> class A(object):
...     __metaclass__=Meta
...     def __init__(self):
...         print "hello"
... 
>>> 
>>> a=A()
hello
>>> A.foo()
foo
>>> a.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'foo'

What's going on here exactly ?

edit: bumping the question

+5  A: 

The rule is like this: when searching for an attribute on an object, the object's class and its parent classes are considered as well. An object's class's metaclass, however, is not considered. When you access an attribute of a class, the class's class is the metaclass, so it is considered. The fallback from object to its class does not trigger a "normal" attribute lookup on the class: for instance, descriptors are called differently whether an attribute is accessed on an instance or its class.

Methods are attributes that are callable (and have a __get__ method that makes 'self' be passed automatically.) That makes it so that methods on the metaclass are like classmethods if you call them on the class, but not available on the instance.

Thomas Wouters
ok but the class of the object A is generated through instantiation of the metaclass. hence, A (the class object) has the method foo, and any instance of A should lookup its class to resolve the methods...Wait.. I try to re-read you.
Stefano Borini
@Stefano: A *doesn't* have the method; its class does.
Ignacio Vazquez-Abrams
The class does not have the attribute. The metaclass has the attribute. You can access the metaclass attribute through the class, but it's not the same thing as the class having the attribute. Because the metaclass and not the class has the attribute, instances of the class do not get access to the attribute.
Thomas Wouters
+9  A: 

You raise a good point.

Here is a good referenceto get a better understanding of the relations between objects, classes and metaclasses:

I also find this reference on descriptors to be quite enlightening about the mechanism of look-up in python.

But I can't say I understand why a.foo fails when A.foo succeeds. It seems that when you look up an attribute of an object, and python does not find it there, it does not exactly look up the attribute in the class, because if it did, it would find A.foo.

EDIT:

Oh! I think I got it. It is due to how inheritance works. If you consider the schema provided by the above link, it looks like this:

alt text

Schematically, it boils down to:

type -- object
  |       |
Meta --   A  -- a

Going left means going to the class of a given instance. Going up means going to the parent.

Now the inheritance mechanism makes the look-up mechanism make a right turn in the schema above. It goes a → A → object. It must do so in order to follow the inheritance rule! To make it clear, the search path is:

 object
   ^
   |
   A  <-- a

Then, clearly, the attribute foo will not be found.

When you lookup for the attribute foo in A, however, it is found, because the lookup path is:

type
  ^
  |       
Meta <--   A 

It all makes sense when one thinks of how inheritance works.

Olivier
+1 Interesting linked documents, ta.
MattH
oh yes, that document. I read it a long time ago. time to re-read it. Thanks.
Stefano Borini
thanks for the link
Matt Joiner
A: 

The way I understand it is that Meta is a class, and A is an instance of it. Thus, when you call A.foo(), it checks through the object and its class. So, when trying A.foo, it first looks through the methods A itself holds, and then the methods of its class, Meta. As A holds no method foo itself, it uses the one of Meta, and so really performs Meta.foo(A).

Similarly, when a.foo is tried, it will first look through a. As a holds no method foo, it will look through A. But A also holds no method foo, as foo is held in Meta. As neither a nor A holds foo, it will raise an AttributeError.

I tried this with a variable as well as a function, putting into the class Meta an attribute txt='txt', and this also was accessible by A, but not a. So, I am inclined to think that I am right in my understanding, but I am just guessing.

Nikwin