>>> 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__()中引发异常的情况下才会被调用。