>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:42:42
运行环境:
Linux发行版本: Debian GNU/Linux 12 (bookworm)
操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
Python版本: 3.11.2
6.7. 区别 __getattr__()
和 __getattribute__()
方法¶
__getattr__()
和__getattribute__()
都可以用做实例属性的获取和拦截(注意,仅对实例属
性(instance
variable)有效,非类属性),__getattr__()
适用于米定义的属性,即该属性在实例
中以及对应的类的基类以及祖先类中都不存在,而__getattribute__()
对于所有属性的访问都
会调用该方法。它们的函数签名分别为:
__getattr__:__getattr__(self,name) __getattribute__:__getattribute__(self,name)
其中参数name为属性的名称。需要注意的是__getattribute__()
仅应用于新式类。既然这两种方法都用作属性的访问,那么它们有什么区別呢?我们来看一个例子e
>>> class A(object):
>>> def __init__(self,name):
>>> self.name=name
>>> a=A("attribute")
>>> print(a.name)
>>> print(a.test)
>>> # 上面的程序输出结果如下:
attribute
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-1-716c7c04fe8f> in <module>()
4 a=A("attribute")
5 print(a.name)
----> 6 print(a.test)
7 # 上面的程序输出结果如下:
AttributeError: 'A' object has no attribute 'test'
当访问一个不存在的实例属性的时候就会抛出AttributeErro异常。这个异常是由内部方
法__getattribute__(self,name)
抛出的,因为__getattribute__()
会被无条件调用,也就是说只
要涉及实例属性的访问就会调用该方法,它要么返回实际的值,要么抛出异常。Python的文
构 http://docs.python.org/2/reference/datamodel,html#object.getattribute
中也提到了这一点。那
么__getattr__()
会在什么情况下调用呢?我们在上面的例子中添加__getattr__()
方法试试。
>>> def __getattr__(self,name):
>>> print("Calling __getattr__:",name)
再次运行程序会发现输出为:
attribute ('Calling __getattr__', 'test') None
这次程序没有抛出异常,而是调用了
__getattr__()
方法。实际上__getattr__()
方法仅如下
情况下才被调用:属件不在实例的__dict__
中;属性不在其基类以及祖先类的__dict__()
中;
触发AttributeError异常时(注意,不仅仅是__getattribute__()
引发的AttributeError异常,
property中定义的get()方法抛出异常的时候也会调用该方法)。需要特別注意的是当这两个
方法同时被定义的时候,要么在__getattribute__()
中显式调用,要么触发AttributeError异
常.否则__getattr__()
永远不会被调用。__getattribute__()
及__getattr__()
方法都是Object类
中定义的默认方法,当用户需要覆盖这些方法时有以下几点注意事项:
1)避免无穷递归。当在上述例子中添__getattribute__()
方法后程序运行会抛出RimtimeError
异常提示 “ RuntimeError:maximum recursion depth exceeded.”。
>>> def __getattribute__(self,attr):
>>> try:
>>> return self.__dict__[attr]
>>> except KeyError:
>>> return 'default'
这是因为属性的访问调用的是獲盖了的__getattribute__()
方法,而该方法中self.__dict__[attr]
又要调用__getattribute__(self,attr)
,于是产生了无穷递归,即使将语句self.__dict__[attr]
替换为
self.__getattribute__(self,attr)
和
getattr(self,attr)
也不能解决问题。正确的 做法是使用
super(obj,sdf).__getattribute__(attr)
,因此上面的例子可以改为:super(A,self).__getattribute__(attr)
或者
object.__getattribute__(self,attr)
。
无穷递归是覆盖__getatt__()
和__getattribute__()
方法的时候需要特别小心。
2
)访问未定义的M性。如果在__getattr__()
方法中不抛出AttributeError异常或者显式
返回一个值,则会返回None,此时可能会影响到程序的实际运行预期。我们来看一个示例:
>>> class A(object):
>>> def __init__(self,name):
>>> self.name=name
>>> self.x =20
>>> def __getattr__(self,name):
>>> print("calling __getattr__:",name)
>>> if name == 'z':
>>> return self.x ** 2
>>> elif name =='y':
>>> return self.x ** 3
>>> def __getattribute__(self,attr):
>>> try:
>>> return super(A,self).__getattribute__(attr)
>>> except KeyError:
>>> return 'default'
>>> a=A('attribute')
>>> print(a.name)
>>> print(a.z)
>>> if hasattr(a,'t'):
>>> c = a.t
>>> print (c)
>>> else:
>>> print("instance a has no attibute t")
attribute
calling __getattr__: z
400
calling __getattr__: t
calling __getattr__: t
None
用户本来的意图是:如果t不属于实例属性,则打印出警告信息,否则给c赋值。按照
用户的理解本来应该是输出警告信息的,可是实际却输出None。这是因为在__getattr__()
方法中没有抛出任何异常也没有显式返回一个值,None被作为默认值返回并动态添加了属
性t,因此hasattr(object,name)的返回结果是True。如果我们在上述例子中抛出异常(raiseTypeError(‘unknown
attr:’ + name)),则一切将如用户期待的那样。
另外关于__getattr__()
和__getattribute__()
有以下两点提醒:
1)覆盖了__getattribute__()
方法之后,任何属性的访问都会调用用户定义的__getattribute__()
方法,性能上会有所损耗,比使用默认的方法要慢。
2)覆盖的__getattr__()
方法如果能够动态处理事先未定义的属性,可以更好地实现数据
隐藏。因为dir()通常只显示正常的M性和方法,因此不会将该属性列为可用属性,上述例子
中如果动态添加属性y,即使hasattr(a,y)的值为True,dir(a)得到的却是如下输出;
['__class__','__delattr__','__dict__','__doc__','__format__','__getattr__','__getattribute__','__hash__','__init__','__module__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__sizeof__','__str__','__subclasshook__','__weakref__','name','x',]
再来思考一个问题:我们知道property也能控制属性的访问,如果一个类中同时定义了
property.
__getattribute__()
以及__getattr__()
来对厲性进行访问控制,那么具体的査找顺序是怎样的呢?
>>> class A(object):
>>> _c= "test"
>>> def __init__(self):
>>> self.x =None
>>> @property
>>> def a(self):
>>> print("using property to access attribute")
>>> if self.x is None:
>>> print("return value")
>>> return 'a'
>>> else:
>>> print("error occured")
>>> raise AttributeError
>>> @a.setter
>>> def a(self,value):
>>> self.x=value
>>> def __getattr__(self,name):
>>> print("using __getattr__ to access attribute")
>>> print("attribute name:",name)
>>> return "b"
>>> def __getattribute__(self,name):
>>> print("using __getattribute__ to access attribute")
>>> return object.__getattribute__(self,name)
>>> a1=A()
>>> print (a1.a)
>>> print("---------------")
>>> a1.a =1
>>> print(a1.a)
>>> print("-------------")
>>> print (A._c)
>>>
>>> # 上述程序的输出如下:
using __getattribute__ to access attribute
using property to access attribute
using __getattribute__ to access attribute
return value
a
---------------
using __getattribute__ to access attribute
using property to access attribute
using __getattribute__ to access attribute
error occured
using __getattr__ to access attribute
attribute name: a
b
-------------
test
当实例化al时由于其默认的厲性x为None,当我们访问
al.a时.最先捜索的是__getattribute__()
方法,由于a是一个property对象,并不存在于al的diet中,因此并不能返
回该方法,此时会搜索property中定义的get()方法,所以返回的结果是a。当用property中
的set()方法对x进行修改并再次访问property的get()方法时会抛出异常,这种情况下会触
发对__getattr__()
方法的调用并返回结果b。程序最后访问类变量输出test是为了说明对类
变量的访问不会涉及__getattribute__()
和__getattr__()
方法。
注意:__getattribute__()
总会被调用,而__getattribute__()
中引发异常的情况下才会被调用。