类元编程中作用于类实例的元类方法

13
我想知道在元类上声明的方法会发生什么。我原以为在元类上声明一个方法会变成一个类方法,但实际情况并非如此。示例:

我想知道在元类上声明的方法会发生什么。我原以为在元类上声明一个方法会变成一个类方法,但实际情况并非如此。

>>> 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'

这里到底发生了什么?

编辑:将问题推到前面。


这个与元类有关的问题:https://dev59.com/FXI-5IYBdhLWcg3wm5pH 是否是同一个根本性的问题? - Jason D
@Stefano: 那么,你对我的回答不满意吗?:-) - Olivier Verdier
@Olivier:绝对的!那很清晰和线性。感谢您,以及所有其他人。 - Stefano Borini
4个回答

17

你提出了一个很好的观点。

这里有一个好的参考,可以更好地理解对象、类和元类之间的关系:

我也发现关于描述符的这个参考对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 

当我们考虑继承的工作原理时,一切都变得合理。


10

规则如下:在搜索对象属性时,会考虑对象的类及其父类。但是不会考虑对象类的元类。当访问类的属性时,该类的类是元类,所以它是被考虑的。从对象到其类的回退不会触发“普通”属性查找:例如,描述符在实例或其类上访问属性时调用方式不同。

方法是可调用的属性(并具有使“self”自动传递的__get__方法)。这使得元类上的方法在使用类调用时类似于类方法,但在实例上不可用。


好的,但是对象A的类是通过元类的实例化生成的。因此,A(类对象)具有方法foo,任何A的实例都应该查找其类以解析方法...等待一下,我尝试重新阅读您的内容。 - Stefano Borini
@Stefano:A对象本身并没有这个方法;它的类才有。 - Ignacio Vazquez-Abrams
类没有这个属性。元类有这个属性。你可以通过类访问元类的属性,但这并不意味着类本身具有该属性。因为属性属于元类而不是类本身,所以类的实例无法访问该属性。 - Thomas Wouters

0
据我理解,Meta是一个类,而A是它的一个实例。因此,当你调用A.foo()时,它会检查对象及其类。所以,在尝试A.foo时,它首先查找A本身持有的方法,然后查找它的类Meta的方法。由于A本身没有foo方法,它使用Meta的方法,从而真正执行Meta.foo(A)。
同样地,当尝试a.foo时,它将首先查找a。由于a没有foo方法,它将查找A。但是A也没有foo方法,因为foo在Meta中。由于a和A都没有foo,它将引发AttributeError异常。
我还尝试了在类Meta中添加一个名为txt的属性,这个属性对A可见,但对a不可见。因此,我倾向于认为我的理解是正确的,但我只是猜测而已。

0

被接受的答案中的链接已经消失,至少对我来说是这样。因此我想补充一些内容:

3. 数据模型 —— object.__getattribute__

并提供我认为的两个关键点:

  • object.__getattribute__ 控制实例属性的访问。
  • type.__getattribute__ 控制类属性的访问。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接