新旧样式类的__getattr__不对称行为

8 投票
3 回答
1406 浏览
提问于 2025-04-16 02:22

这是我第一次在这里发言,如果信息有点杂乱或者太长了,请见谅。

我想更深入地了解对象的属性是如何在需要时被获取的。所以我阅读了Python 2.7的文档,标题是“数据模型”,在这里我遇到了__getattr__。为了检查我是否理解了它的行为,我写了一些简单(但不完整)的字符串包装类。

class OldStr:
  def __init__(self,val):
    self.field=val

  def __getattr__(self,name):
    print "method __getattr__, attribute requested "+name

class NewStr(object):
  def __init__(self,val):  
    self.field=val

  def __getattr__(self,name):
    print "method __getattr__, attribute requested "+name

你可以看到这两个类除了一个是旧式类,一个是新式类之外,其他都是一样的。文中提到__getattr__是在“常规地方找不到属性时被调用”,我想尝试对这两个类的实例进行加法操作,看看会发生什么,期待它们的行为是一样的。

但是我得到的结果让我有点困惑:

>>> x=OldStr("test")
>>> x+x
method __getattr__, attribute requested __coerce__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

很好!我没有为__coerce__定义方法(虽然我本来是期待会调用__add__,没关系 :),所以__getattr__参与了进来,返回了一个没用的东西。但接下来

>>> y=NewStr("test")
>>> y+y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NewStr' and 'NewStr'

为什么在使用像+这样的内置操作符时,旧式类和新式类的__getattr__表现会不一样呢?能不能有人帮我理解一下发生了什么,以及我在阅读文档时的错误在哪里?

非常感谢,你们的帮助我非常感激!

3 个回答

1

你的 __getattr__ 函数没有返回任何东西。我不太明白为什么旧式类在查找 __add__ 之前会先执行 __getattr__,但它确实是这么做的,而这就导致它试图调用返回值,而这个返回值是 None

新式类的做法是对的:因为你没有定义 __add__,所以它不知道该怎么进行加法运算。

5

请查看 新式类的特殊方法查找

特殊方法会直接在类对象和实例对象中查找,因此 __getattr__()__getattribute__() 这两个方法会被跳过。正因为这个原因,像 instance.__add__ = foobar 这样的写法是无效的。

这样做是为了加快属性的访问速度。特殊方法被调用的频率非常高,如果让它们遵循标准的、相对复杂的属性查找方式,会显著拖慢解释器的运行速度。

对于旧式类来说,属性查找要简单得多(尤其是旧式类不支持描述符),所以在旧式类中没有必要以不同的方式处理特殊方法。

撰写回答