我试图了解何时使用__getattr__
or __getattribute__
。文档中提到的__getattribute__
适用于新式类。什么是新式班?
8 回答
__getattr__
和之间的一个关键区别__getattribute__
是__getattr__
只有在没有以通常方式找到属性时才会调用它。它有利于实现缺失属性的后备,并且可能是您想要的两个之一。
__getattribute__
在查看对象的实际属性之前调用,因此正确实现可能很棘手。您可以很容易地以无限递归结束。
新式类派生自object
,旧式类是 Python 2.x 中没有显式基类的类。但是在 和 之间进行选择时,旧式和新式类之间的区别并不是重要__getattr__
的__getattribute__
。
你几乎肯定想要__getattr__
。
让我们看一些简单的例子__getattr__
和__getattribute__
魔术方法。
__getattr__
__getattr__
每当您请求尚未定义的属性时,Python 都会调用 方法。在下面的示例中,我的类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)
这只是一个基于Ned Batchelder 解释的示例。
__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
新样式类继承自object
或另一个新样式类:
class SomeObject(object):
pass
class SubObject(SomeObject):
pass
旧式课程不会:
class SomeObject:
pass
这仅适用于 Python 2 - 在 Python 3 中,以上所有内容都将创建新样式的类。
请参阅9. 类(Python 教程)、NewClassVsClassicClass和Python中旧样式和新样式类的区别是什么?详情。
新式类是“对象”的子类(直接或间接)。除了具有__new__
类方法之外,它们还__init__
具有更合理的低级行为。
通常,您会想要覆盖__getattr__
(如果您要覆盖其中任何一个),否则您将很难在方法中支持“self.foo”语法。
额外信息: http: //www.devx.com/opensource/Article/31482/0/page/4
- getattribute:用于从实例中检索属性。它通过使用点表示法或 getattr() 内置函数来捕获访问实例属性的每一次尝试。
- getattr:当在对象中找不到属性时,作为最后一个资源执行。您可以选择返回默认值或引发 AttributeError。
回到__getattribute__函数;如果默认实现没有被覆盖;执行该方法时会进行以下检查:
- 检查MRO链中任意一个类中是否定义了同名(属性名)的描述符(方法对象解析)
- 然后查看实例的命名空间
- 然后查看类命名空间
- 然后进入每个基地的命名空间等等。
- 最后,如果没有找到,默认实现调用实例的后备getattr () 方法,并引发 AttributeError 异常作为默认实现。
这是object.__getattribute__ 方法的实际实现:
.. c:function:: PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *name) 用于放入类型对象的 tp_getattro 槽的通用属性 getter 函数。它在对象的 MRO 中的类字典中查找描述符以及对象的 :attr:~object 中的属性。字典(如果存在)。如 :ref:descriptors 中所述,数据描述符优先于实例属性,而非数据描述符则不然。否则,会引发 :exc:AttributeError 。
在阅读 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__
方法时,将在查找链的末尾调用该方法。这也会使代码更简洁,因为您没有为委托给另一个对象而定义的方法列表。
我发现没有人提到这个区别:
__getattribute__
有一个默认实现,但__getattr__
没有。
class A:
pass
a = A()
a.__getattr__ # error
a.__getattribute__ # return a method-wrapper
这有一个明确的含义:既然__getattribute__
有一个默认实现,虽然__getattr__
没有,但显然 python 鼓励用户实现__getattr__
.