理解 __getattr__ 和 __getattribute__ 之间的区别

272

我正在努力理解__getattr____getattribute__之间的区别,但是我无法做到。

Stack Overflow问题Difference between __getattr__ vs __getattribute__答案说:

__getattribute__在查看对象上的实际属性之前被调用,因此正确实现可能会很棘手。你很容易陷入无限递归。

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

接着它继续说:

你几乎肯定需要__getattr__

为什么?

我读到,如果__getattribute__失败,将调用__getattr__。那么为什么有两种执行相同操作的不同方法?如果我的代码使用新样式类,我应该使用哪种?

我正在寻找一些代码示例来解决这个问题。我已经尽力在谷歌上搜索了,但是我找到的答案没有全面讨论这个问题。

如果有任何文档,我愿意阅读。


2
为避免该方法中的无限递归,其实现应始终使用相同名称调用基类方法以访问其所需的任何属性。例如,object.getattribute(self, name)。[https://docs.python.org/2/reference/datamodel.html#object.__getattribute__] - kmonsoor
可能是Difference between __getattr__ vs __getattribute__的重复问题。 - pppery
4个回答

385

首先了解一些基础知识。

对于对象,您需要处理它们的属性。通常,我们使用instance.attribute来访问对象属性。有时候我们需要更多控制(当事先不知道属性名称时)。

例如,instance.attribute将变为getattr(instance, attribute_name)。使用这种模式,我们可以通过提供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__

__getattribute__始终会被调用。


1
例如,instance.attribute 将变为 getattr(instance, attribute_name)。难道不应该是 __getattribute__(instance, attribute_name) 吗? - Md. Abu Nafee Ibna Zahid
3
getattr是一个内置函数。getattr(foo, 'bar')foo.bar等效。 - wizzwizz4
值得注意的是,__getattr__ 只有在被定义时才会被调用,因为它不是从 object 继承而来的。 - joel

121

__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__,则它永远不会运行,因为f具有a属性。如果它运行了(假设您要求f.b),则不会被调用以查找__dict__,因为它已经存在,只有在所有其他查找属性的方法失败时才会调用__getattr__
使用__getattribute__编写上述类的“正确”方式是:
class Foo(object):
    # Same __init__

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

super().__getattribute__(attr) 将最近的超类(严格来说是在类的方法解析顺序或 MRO 中下一个类)的 __getattribute__ 方法绑定到当前对象 self 上,然后调用它并让它完成工作。

使用 __getattr__ 可以避免所有这些麻烦,它允许 Python 做它通常的事情,直到找不到属性为止。此时,Python 将控制权移交给你的__getattr__方法,并让它想出一个解决方案。

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

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

我会把那个留做一道练习题。


1
当你调用super(Foo, self)时,你是在显式地获取Foo的最近父类,然后将实例self作为第二个参数传递进去吗?既然Python 3使用方法解析顺序来隐式地找出self最相关的父类(在使用多重继承时非常有用),那么你是否可以使用super()而不是super(Foo, self)呢? - E. Kaufman
1
@E.Kaufman,兄弟,答案已经在2010年发布了,你发表这样无知的评论时已经过去十多年了。 - Xiang
编辑答案以遵循PEP 3135; 使用super()而不是super(Foo, self) - Niko Pasanen

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__获得的能力。


18

我已经阅读了他人的出色解释。然而,我在这篇博客Python Magic Methods and __getattr__中找到了一个简单的答案。以下所有内容都来自该博客。

使用 __getattr__ 魔术方法,我们可以拦截那些不存在的属性查找并执行某些操作,以避免失败:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

但如果属性存在,__getattr__ 不会被调用:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute____getattr__ 类似,但有一个重要区别,__getattribute__ 将拦截每一个属性查找,无论属性是否存在。

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

在这个例子中,d对象已经具有属性值。但是当我们尝试访问它时,我们没有得到原来预期的值(“Python”); 我们只得到了__getattribute__返回的任何内容。这意味着我们已经几乎失去了value属性; 它变成了“无法访问的”。

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