理解__getattr__与__getattribute__的区别
我想搞明白 __getattr__
和 __getattribute__
这两个东西有什么区别,但我一直搞不清楚。
在 Stack Overflow 上的一个回答提到:
__getattribute__
在查看对象的实际属性之前就会被调用,所以实现起来可能会有点棘手。你很容易就会陷入无限递归。
我完全不知道这是什么意思。
接着它又说:
你几乎肯定想用
__getattr__
。
为什么呢?
我看到如果 __getattribute__
失败了,就会调用 __getattr__
。那为什么会有两个不同的方法来做同样的事情呢?如果我的代码使用了新风格的类,我应该用哪个呢?
我想找一些代码示例来澄清这个问题。我尽力在网上搜索,但找到的答案都没有深入讨论这个问题。
如果有相关的文档,我也很乐意去阅读。
4 个回答
我觉得其他回答已经很好地解释了 __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__
能带来的强大功能。
__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
试图访问 f
的 a
属性。这会调用 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)
将“最近”的父类的 __getattribute__
方法(正式来说,是类的方法解析顺序中的下一个类,简称 MRO)绑定到当前对象 self
,然后调用它,让它来完成工作。
通过使用 __getattr__
可以避免所有这些麻烦,它让 Python 正常工作,直到找不到某个属性。此时,Python 会将控制权交给你的 __getattr__
方法,让它来处理。
值得注意的是,使用 __getattr__
也可能会遇到无限递归的问题。
class Foo(object):
def __getattr__(self, attr):
return self.attr
这个问题我就留给你自己去练习了。
先了解一些基础知识。
在使用对象的时候,你需要处理它们的属性。通常,我们会用 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__
会在每次访问属性时被调用。