理解__getattr__与__getattribute__的区别

274 投票
4 回答
136230 浏览
提问于 2025-04-16 07:42

我想搞明白 __getattr____getattribute__ 这两个东西有什么区别,但我一直搞不清楚。

在 Stack Overflow 上的一个回答提到:

__getattribute__ 在查看对象的实际属性之前就会被调用,所以实现起来可能会有点棘手。你很容易就会陷入无限递归。

我完全不知道这是什么意思。

接着它又说:

你几乎肯定想用 __getattr__

为什么呢?

我看到如果 __getattribute__ 失败了,就会调用 __getattr__。那为什么会有两个不同的方法来做同样的事情呢?如果我的代码使用了新风格的类,我应该用哪个呢?

我想找一些代码示例来澄清这个问题。我尽力在网上搜索,但找到的答案都没有深入讨论这个问题。

如果有相关的文档,我也很乐意去阅读。

4 个回答

71

我觉得其他回答已经很好地解释了 __getattr____getattribute__ 之间的区别,但有一点可能不太清楚,那就是为什么你会想使用 __getattribute__。其实,__getattribute__ 的一个很酷的地方在于,它让你可以重载(也就是重新定义)点号操作,这样在访问一个类的时候,你可以自定义属性的访问方式,做到更底层的控制。

举个例子,假设我想定义一个类,让所有只接受一个 self 参数的方法都被当作属性来处理:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super().__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

然后在交互式解释器中:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

当然,这只是个搞笑的例子,你可能根本不会想这么做,但它展示了重载 __getattribute__ 能带来的强大功能。

122

__getattribute__ 是在访问属性时会被调用的一个方法。

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

这会导致无限递归。问题出在这一行 return self.__dict__[attr]。我们假设(这和事实差不多)所有的属性都存储在 self.__dict__ 中,并且可以通过它们的名字来访问。那一行

f.a

试图访问 fa 属性。这会调用 f.__getattribute__('a')。接着 __getattribute__ 会尝试加载 self.__dict____dict__self == f 的一个属性,所以 Python 会调用 f.__getattribute__('__dict__'),这又会试图访问 '__dict__' 属性。这就造成了无限递归。

如果使用的是 __getattr__,那么

  1. 它根本不会运行,因为 f 有一个 a 属性。
  2. 如果它运行了(假设你请求的是 f.b),那么它不会去查找 __dict__,因为它已经存在了,而 __getattr__ 只有在 所有其他查找属性的方法都失败 时才会被调用。

使用 __getattribute__ 正确编写上述类的方法是

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super().__getattribute__(attr)

super().__getattribute__(attr) 将“最近”的父类的 __getattribute__ 方法(正式来说,是类的方法解析顺序中的下一个类,简称 MRO)绑定到当前对象 self,然后调用它,让它来完成工作。

通过使用 __getattr__ 可以避免所有这些麻烦,它让 Python 正常工作,直到找不到某个属性。此时,Python 会将控制权交给你的 __getattr__ 方法,让它来处理。

值得注意的是,使用 __getattr__ 也可能会遇到无限递归的问题。

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

这个问题我就留给你自己去练习了。

387

先了解一些基础知识。

在使用对象的时候,你需要处理它们的属性。通常,我们会用 instance.attribute 来访问这些属性。有时候,我们需要更多的控制,比如当我们事先不知道属性的名字时。

比如,instance.attribute 可以用 getattr(instance, attribute_name) 来替代。通过这种方式,我们可以把属性名作为字符串传入,从而获取到对应的属性。

使用 __getattr__

你还可以告诉一个类如何处理那些它没有明确管理的属性,这可以通过 __getattr__ 方法来实现。

每当你请求一个还没有定义的属性时,Python 就会调用这个方法,这样你就可以定义该怎么处理这个属性。

一个经典的使用场景是:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

注意事项和 __getattribute__ 的使用

如果你想捕捉每一个属性,不管它是否存在,就要使用 __getattribute__。它和 __getattr__ 的区别在于,__getattr__ 只会在请求的属性实际上不存在时被调用。如果你直接设置了一个属性,引用这个属性时会直接获取到它,而不会调用 __getattr__

__getattribute__ 会在每次访问属性时被调用。

撰写回答