类实例上的 метаклассов方法

13 投票
4 回答
1293 浏览
提问于 2025-04-15 19:08

我在想,元类上声明的方法会发生什么。我原本以为,如果在元类上声明一个方法,它会变成一个类方法,但实际情况却有所不同。举个例子:

>>> 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 个回答

0

我理解的意思是,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却不能。所以,我倾向于认为我的理解是对的,不过我也只是猜测。

10

这个规则是这样的:当你在一个对象上查找某个属性时,不仅要考虑这个对象的类,还要考虑它的父类。不过,注意一个点:对象的类的元类是被考虑的。当你访问一个类的属性时,这个类的类就是元类,所以它被考虑的。从对象到它的类的回退并不会触发“正常”的属性查找:举个例子,当你在实例上访问属性和在类上访问属性时,描述符的调用方式是不同的。

方法是可以调用的属性(并且有一个__get__方法,可以自动传递'self')。这就意味着,如果你在类上调用元类的方法,它们就像类方法一样,但在实例上是不可用的。

17

你提了个好问题。

这里有一个不错的参考资料,可以帮助你更好地理解对象、类和 metaclass 之间的关系:

我还发现这个关于描述符的资料对 Python 中的查找机制也很有启发。

不过,我不太明白为什么 a.foo 会失败,而 A.foo 却成功。看起来当你查找一个对象的属性时,如果 Python 在对象里找不到,它并不会完全去类里查找这个属性,因为如果它真的去查找的话,就应该能找到 A.foo

编辑:

哦!我想我明白了。这是因为继承的工作方式。如果你参考上面的链接提供的示意图,它看起来是这样的:

alt text

从示意图来看,实际上可以简化为:

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

走意味着去某个实例的类。向走意味着去父类

现在,继承机制让查找机制在上面的示意图中做了一个右转。它的查找顺序是 a → A → object。必须这样做才能遵循继承规则!为了更清楚,查找路径是:

 object
   ^
   |
   A  <-- a

那么,很明显,属性 foo 是找不到的。

但是,当你在 A 中查找属性 foo 时,它能找到的,因为查找路径是:

type
  ^
  |       
Meta <--   A 

当人们考虑继承是如何工作的时,这一切就都说得通了。

撰写回答