了解__getattr__和__getattribute__之间的区别


206

我试图理解上的差异之间__getattr____getattribute__,但是,我在它失败。

堆栈溢出问题的答案与vs 之间的区别是__getattr____getattribute__

__getattribute__在查看对象的实际属性之前调用,因此很难正确实现。您可以非常轻松地进行无限递归。

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

然后继续说:

您几乎可以肯定想要__getattr__

为什么?

我读到,如果__getattribute__失败,__getattr__则称为。那么,为什么有两种不同的方法做同样的事情呢?如果我的代码实现了新样式类,我应该使用什么?

我正在寻找一些代码示例来清除此问题。我已尽我所能搜索Google,但是我发现的答案并未彻底讨论该问题。

如果有任何文档,我准备阅读。


2
如果有帮助,请从文档“为了避免此方法中的无限递归,其实现应始终调用具有相同名称的基类方法以访问所需的任何属性,例如object .__ getattribute __(self,name)。 ” [ docs.python.org/2/reference/...
kmonsoor

Answers:


306

首先了解一些基础知识。

对于对象,您需要处理其属性。通常我们会这么做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__ 一直被称为。


“例如,instance.attribute将变为getattr(instance, attribute_name)。” 不是__getattribute__(instance, attribute_name)吗?
阿布·纳菲·伊卜纳·扎希德(Abu Nafee)女士

1
@ Md.AbuNafeeIbnaZahid getattr是内置函数。getattr(foo, 'bar')等价于foo.bar
wizzwizz4

值得注意的是,__getattr__只有在定义的情况下才会被调用,因为它不是从继承而来的object
joelb

94

__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

尝试访问的a属性f。这叫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(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)__getattribute__“最近”超类的方法(self通常是该类的“方法解析顺序”中的下一个类)绑定到当前对象,然后调用它并让其完成工作。

通过使用__getattr__python 可以在没有找到属性之前将其正常处理,从而避免了所有这些麻烦。到那时,Python将控制权交给您的__getattr__方法,并让它提出一些建议。

还值得注意的是,您可以使用进行无限递归__getattr__

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

我将把它留作练习。


60

我认为其他的答案做了解释之间的差异的一个伟大的工作__getattr____getattribute__,但有一点可能没有明确的是,为什么你会想使用__getattribute__。有趣的__getattribute__是,它本质上允许您在访问类时重载点。这使您可以自定义如何在较低级别访问属性。例如,假设我要定义一个类,其中所有仅带有自变量的方法都被视为属性:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__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__


6

我经历了别人的出色解释。但是,我从此博客Python Magic Methods和__getattr__找到了一个简单的答案。以下所有都是从那里开始的。

使用__getattr__magic方法,我们可以拦截不存在的属性查找并做一些事情,以确保它不会失败:

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属性;它已经变得“无法到达”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.