【发布时间】:2010-07-19 02:11:19
【问题描述】:
我试图了解何时使用__getattr__ 或__getattribute__。 documentation 提到 __getattribute__ 适用于新式类。什么是新式班?
【问题讨论】:
-
@Trilarion 但这个问题提到了这里的答案......
标签: python getattr getattribute
我试图了解何时使用__getattr__ 或__getattribute__。 documentation 提到 __getattribute__ 适用于新式类。什么是新式班?
【问题讨论】:
标签: python getattr getattribute
__getattr__ 和 __getattribute__ 之间的主要区别在于,__getattr__ 仅在没有以通常方式找到属性时才被调用。这对于实现缺失属性的回退很有用,并且可能是您想要的两个之一。
__getattribute__ 在查看对象的实际属性之前被调用,因此正确实现可能很棘手。您很容易陷入无限递归。
新式类派生自object,旧式类是 Python 2.x 中没有显式基类的类。但是在__getattr__ 和__getattribute__ 之间进行选择时,旧式和新式类之间的区别并不重要。
你几乎肯定想要__getattr__。
【讨论】:
__getattribute__,__getattr__ 将在__getattribute__ 引发AttributeError 的时候调用。为什么不将所有内容合二为一?
__getattribute__。
__getattribute__ 中的变量而不调用无限递归,必须调用super().__getattribute__(item)
让我们看看__getattr__ 和__getattribute__ 魔术方法的一些简单示例。
__getattr__每当您请求尚未定义的属性时,Python 都会调用__getattr__ 方法。在以下示例中,我的类 Count 没有 __getattr__ 方法。现在,当我尝试访问 obj1.mymin 和 obj1.mymax 属性时,一切正常。但是当我尝试访问obj1.mycurrent 属性时——Python 给了我AttributeError: 'Count' object has no attribute 'mycurrent'
class Count():
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent) --> AttributeError: 'Count' object has no attribute 'mycurrent'
现在我的班级 Count 有 __getattr__ 方法。现在,当我尝试访问 obj1.mycurrent 属性时,python 会返回我在 __getattr__ 方法中实现的任何内容。在我的示例中,每当我尝试调用不存在的属性时,python 都会创建该属性并将其设置为整数值 0。
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
def __getattr__(self, item):
self.__dict__[item]=0
return 0
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)
__getattribute__现在让我们看看__getattribute__ 方法。如果你的类中有__getattribute__ 方法,python 会为每个属性调用这个方法,不管它是否存在。那么为什么我们需要__getattribute__ 方法呢?一个很好的理由是您可以阻止对属性的访问并使它们更加安全,如下例所示。
每当有人尝试访问我以子字符串 'cur' 开头的属性时,python 都会引发 AttributeError 异常。否则返回该属性。
class Count:
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
重要提示:为了避免__getattribute__方法中的无限递归,它的实现应该总是调用同名的基类方法来访问它需要的任何属性。例如:object.__getattribute__(self, name) 或 super().__getattribute__(item) 而不是 self.__dict__[item]
如果您的类同时包含 getattr 和 getattribute 魔术方法,则首先调用 __getattribute__。但是如果__getattribute__ 加注
AttributeError 异常,则异常将被忽略,__getattr__ 方法将被调用。请参见以下示例:
class Count(object):
def __init__(self,mymin,mymax):
self.mymin=mymin
self.mymax=mymax
self.current=None
def __getattr__(self, item):
self.__dict__[item]=0
return 0
def __getattribute__(self, item):
if item.startswith('cur'):
raise AttributeError
return object.__getattribute__(self,item)
# or you can use ---return super().__getattribute__(item)
# note this class subclass object
obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
【讨论】:
__getattribute__ 的具体用例是什么,但肯定不是。因为根据您的示例,如果对象的__dict__ 中不存在该属性,那么您在__getattribute__ 中所做的一切就是引发AttributeError 异常;但您并不真正需要它,因为这是 __getattribute__ 的默认实现,而事实上 __getattr__ 正是您作为后备机制所需要的。
current 是在 Count 的实例上定义的(请参阅 __init__),因此如果该属性不存在,只需提高 AttributeError 就不会发生什么 - 它推迟了到__getattr__,用于所有以“cur”开头的名称,包括current,还有curious、curly ...
这只是基于Ned Batchelder's explanation 的示例。
__getattr__ 示例:
class Foo(object):
def __getattr__(self, attr):
print "looking up", attr
value = 42
self.__dict__[attr] = value
return value
f = Foo()
print f.x
#output >>> looking up x 42
f.x = 3
print f.x
#output >>> 3
print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')
如果与__getattribute__ 一起使用相同的示例,您将得到 >>> RuntimeError: maximum recursion depth exceeded while calling a Python object
【讨论】:
__getattr__() 实现只接受有限的一组有效属性名,方法是针对无效属性名引发AttributeError,从而避免@ 987654322@。此示例无条件地接受 all 属性名称为有效 - __getattr__() 的奇怪(并且坦率地说容易出错)滥用。如果您想像本例中那样“完全控制”属性创建,则需要__getattribute__()。
defaultdict 相同。
__getattr__会被调用之前超类查找。这对于object 的直接子类来说是可以的,因为您真正关心的唯一方法是忽略实例的魔术方法,但是对于任何更复杂的继承结构,您完全删除了从父类继承任何东西的能力。
回到 __getattribute__ 函数;如果默认实现没有被覆盖;执行该方法时会进行以下检查:
这是 object.__getattribute__ 方法的实际实现:
.. c:function:: PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *name) 通用属性 getter 函数,用于 放入类型对象的 tp_getattro 槽中。它寻找一个 对象的 MRO 中的类字典中的描述符 作为对象的 :attr:~object.dict 中的属性(如果 当下)。如 :ref:descriptors 中所述,数据描述符采用 优先于实例属性,而非数据描述符 不。否则,会引发 :exc:AttributeError 。
【讨论】:
我发现没有人提到这个区别:
__getattribute__ 有一个默认实现,但__getattr__ 没有。
class A:
pass
a = A()
a.__getattr__ # error
a.__getattribute__ # return a method-wrapper
这个意思很明确:因为__getattribute__有默认实现,而__getattr__没有,显然python鼓励用户实现__getattr__。
【讨论】:
在阅读 Beazley & Jones PCB 时,我偶然发现了一个明确而实用的 __getattr__ 用例,它有助于回答 OP 问题的“何时”部分。从书中:
“__getattr__() 方法有点像属性查找的包罗万象。如果代码尝试访问不存在的属性,就会调用该方法。”我们从上述答案中知道这一点,但在 PCB 配方 8.15 中,此功能用于实现委托设计模式。如果对象 A 有一个属性对象 B 实现了对象 A 想要委托给的许多方法,而不是重新定义对象 A 中的所有对象 B 的方法只是为了调用对象 B 的方法,定义一个 __getattr__() 方法如下:
def __getattr__(self, name):
return getattr(self._b, name)
其中 _b 是对象 A 的属性名称,即对象 B。当在对象 A 上调用对象 B 上定义的方法时,将在查找链的末尾调用 __getattr__ 方法。这也会使代码更简洁,因为您没有为委托给另一个对象而定义的方法列表。
【讨论】:
新样式类继承自object,或从另一个新样式类:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
旧式类不会:
class SomeObject:
pass
这仅适用于 Python 2 - 在 Python 3 中,以上所有内容都将创建新样式的类。
有关详细信息,请参阅 9. Classes(Python 教程)、NewClassVsClassicClass 和 What is the difference between old style and new style classes in Python?。
【讨论】:
新式类是“对象”的子类(直接或间接)。除了__init__ 之外,它们还有一个__new__ 类方法,并且具有更合理的低级行为。
通常,您需要覆盖 __getattr__(如果您要覆盖其中任何一个),否则您将很难在方法中支持“self.foo”语法。
【讨论】: