类实例上的 метаклассов方法
我在想,元类上声明的方法会发生什么。我原本以为,如果在元类上声明一个方法,它会变成一个类方法,但实际情况却有所不同。举个例子:
>>> class A(object):
... @classmethod
... def foo(cls):
... print "foo"
...
>>> a=A()
>>> a.foo()
foo
>>> A.foo()
foo
不过,如果我尝试定义一个元类,并给它一个方法 foo,似乎这个方法对类有效,而不是对实例有效。
>>> 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'
这到底是怎么回事呢?
编辑: 提醒一下这个问题
4 个回答
我理解的意思是,Meta是一个类,而A是这个类的一个实例。也就是说,当你调用A.foo()的时候,它会先在A这个对象里查找,然后再查找它所属的类Meta。所以,当你尝试A.foo时,它会先看看A自己有没有这个方法,如果没有,就去Meta里找。因为A自己没有foo这个方法,所以它就用Meta里的那个方法,实际上是执行Meta.foo(A)。
同样地,当你尝试a.foo时,它会先在a里查找。因为a里没有foo这个方法,它就去A里找。但是A里也没有foo,因为这个方法是在Meta里。由于a和A都没有foo这个方法,所以会报一个AttributeError的错误。
我还试过用一个变量和一个函数,在Meta这个类里放了一个属性txt='txt',这个属性A可以访问,但a却不能。所以,我倾向于认为我的理解是对的,不过我也只是猜测。
这个规则是这样的:当你在一个对象上查找某个属性时,不仅要考虑这个对象的类,还要考虑它的父类。不过,注意一个点:对象的类的元类是不被考虑的。当你访问一个类的属性时,这个类的类就是元类,所以它是被考虑的。从对象到它的类的回退并不会触发“正常”的属性查找:举个例子,当你在实例上访问属性和在类上访问属性时,描述符的调用方式是不同的。
方法是可以调用的属性(并且有一个__get__
方法,可以自动传递'self')。这就意味着,如果你在类上调用元类的方法,它们就像类方法一样,但在实例上是不可用的。
你提了个好问题。
这里有一个不错的参考资料,可以帮助你更好地理解对象、类和 metaclass 之间的关系:
我还发现这个关于描述符的资料对 Python 中的查找机制也很有启发。
不过,我不太明白为什么 a.foo
会失败,而 A.foo
却成功。看起来当你查找一个对象的属性时,如果 Python 在对象里找不到,它并不会完全去类里查找这个属性,因为如果它真的去查找的话,就应该能找到 A.foo
。
编辑:
哦!我想我明白了。这是因为继承的工作方式。如果你参考上面的链接提供的示意图,它看起来是这样的:
从示意图来看,实际上可以简化为:
type -- object
| |
Meta -- A -- a
向左走意味着去某个实例的类。向上走意味着去父类。
现在,继承机制让查找机制在上面的示意图中做了一个右转。它的查找顺序是 a → A → object
。必须这样做才能遵循继承规则!为了更清楚,查找路径是:
object
^
|
A <-- a
那么,很明显,属性 foo
是找不到的。
但是,当你在 A
中查找属性 foo
时,它是能找到的,因为查找路径是:
type
^
|
Meta <-- A
当人们考虑继承是如何工作的时,这一切就都说得通了。