250

我试图理解 和 之间的区别__getattr____getattribute__但是,我失败了。

堆栈溢出问题的答案vs之间的差异__getattr____getattribute__说:

__getattribute__在查看对象的实际属性之前调用,因此正确实现可能很棘手。您可以很容易地以无限递归结束。

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

然后它继续说:

你几乎肯定想要__getattr__

为什么?

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

我正在寻找一些代码示例来解决这个问题。我已经尽我所能在谷歌上搜索,但我找到的答案并没有彻底讨论这个问题。

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

4

4 回答 4

356

先说一些基础知识。

对于对象,您需要处理它们的属性。通常,我们会这样做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__.

__getattribute__一直被称为。

于 2010-11-28T06:55:37.297 回答
114

__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__将“最近”超类的方法(正式地,类的方法解析顺序或 MRO 中的下一个类)绑定到当前对象self,然后调用它并让它完成工作。

通过使用__getattr__which 可以避免所有这些麻烦,这让 Python可以正常工作,直到找不到属性为止。那时,Python 将控制权交给您的__getattr__方法并让它想出一些东西。

还值得注意的是,您可能会遇到无限递归__getattr__

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

我会把那个留作练习。

于 2010-11-28T07:00:31.990 回答
64

__getattr__我认为其他答案在解释and之间的区别方面做得很好__getattribute__,但是可能不清楚的一件事是您为什么要使用__getattribute__. 很酷的一点__getattribute__是,它本质上允许您在访问类时重载点。这允许您自定义如何在低级别访问属性。例如,假设我想定义一个类,其中所有只接受 self 参数的方法都被视为属性:

# 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__

于 2010-11-28T07:35:08.293 回答
10

I have gone through other's excellent explanation. However, I found a simple answer from this blog Python Magic Methods and __getattr__. All the following are from there.

Using the __getattr__ magic method, we can intercept that inexistent attribute lookup and do something so it doesn’t fail:

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'

But if the attribute does exist, __getattr__ won’t be invoked:

class Dummy(object):

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

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

__getattribute__ is similar to __getattr__, with the important difference that __getattribute__ will intercept EVERY attribute lookup, doesn’t matter if the attribute exists or not.

class Dummy(object):

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

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

In that example, the d object already has an attribute value. But when we try to access it, we don’t get the original expected value (“Python”); we’re just getting whatever __getattribute__ returned. It means that we’ve virtually lost the value attribute; it has become “unreachable”.

于 2020-01-17T03:59:07.090 回答