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 开发者_JS百科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
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:
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.
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.
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.
The links in the accepted answer have gone, at least for me. So I want to make some supplement:
3. Data model — object.__getattribute__
and give two key points in my opinion:
object.__getattribute__
control instance's attribute access.type.__getattribute__
control class's attribute access.
精彩评论