python 了解__getattr__和__getattribute__之间的区别

kzipqqlq  于 2023-02-02  发布在  Python
关注(0)|答案(4)|浏览(150)

我试图理解__getattr____getattribute__之间的区别,但是,我失败了。
堆栈溢出问题 * Difference between __getattr__ vs __getattribute__ * 的answer表示:
__getattribute__是在查看对象的实际属性之前调用的,因此要正确实现它可能很棘手,很容易导致无限递归。
我完全不知道那是什么意思。
然后它继续说:
你几乎肯定想要__getattr__
为什么?
我读到如果__getattribute__失败,__getattr__被调用。那么为什么有两个不同的方法做同样的事情?如果我的代码实现了新样式的类,我应该使用什么?
我正在寻找一些代码的例子,以明确这个问题。我已经谷歌我的能力,但我发现的答案没有彻底讨论这个问题。
如果有任何文件,我准备阅读。

holgip5t

holgip5t1#

我已经看过了别人的精彩解释,但是我在这个博客Python Magic Methods and __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__将拦截EVERY属性查找,而不管属性是否存在。

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属性;它就变得“不可达”。

goqiplq2

goqiplq22#

先来点基础知识。

对于对象,你需要处理它们的属性,通常我们是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__一直被调用。

mwngjboj

mwngjboj3#

只要发生属性访问,就调用__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__')f.__getattribute__('__dict__')将再次尝试访问属性'__dict__ '。这是无限递归。
如果使用的是__getattr__,则
1.因为f具有a属性,所以它永远不会运行。
1.如果它已经运行了,(假设你要求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

我就把这个当作练习吧。

fjnneemd

fjnneemd4#

我认为其他的答案已经很好地解释了__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__可以获得的强大功能。

相关问题