返回介绍

建议60:区别 __getattr__() 和 __getattribute__() 方法

发布于 2024-01-30 22:19:09 字数 5545 浏览 0 评论 0 收藏 0

__getattr__()和__getattribute__()都可以用做实例属性的获取和拦截(注意,仅对实例属性(instance variable)有效,非类属性),__getattr__()适用于未定义的属性,即该属性在实例中以及对应的类的基类以及祖先类中都不存在,而__getattribute__()对于所有属性的访问都会调用该方法。它们的函数签名分别为:

__getattr__: __getattr__(self,name)
__getattribute__: __getattribute__(self,name)

其中参数name为属性的名称。需要注意的是__getattribute__()仅应用于新式类。

既然这两种方法都用作属性的访问,那么它们有什么区别呢?我们来看一个例子。

class A(object):
  def __init__(self,name):
    self.name = name
a = A("attribute")
print a.name
print a.test

上面的程序输出如下:

attribute
Traceback (most recent call last):
  File "test.py", line 7, in <module>
   print a.test
AttributeError: 'A' object has no attribute 'test'

当访问一个不存在的实例属性的时候就会抛出AttributeError异常。这个异常是由内部方法__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__()方法后程序运行会抛出RuntimeError异常提示“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,self).__getattribute__(attr),因此上面的例子可以改为:super(A,self).__getattribute__(attr)或者object.__getattribute__(self,attr)。无穷递归是覆盖__getatt__()和__getattribute__()方法的时候需要特别小心。

2)访问未定义的属性。如果在__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 attribute t"

用户本来的意图是:如果t不属于实例属性,则打印出警告信息,否则给c赋值。按照用户的理解本来应该是输出警告信息的,可是实际却输出None。这是因为在__getattr__()方法中没有抛出任何异常也没有显式返回一个值,None被作为默认值返回并动态添加了属性t,因此hasattr(object,name)的返回结果是True。如果我们在上述例子中抛出异常(raise TypeError('unknown attr:' + name)),则一切将如用户期待的那样。

另外关于__getattr__()和__getattribute__()有以下两点提醒:

1)覆盖了__getattribute__()方法之后,任何属性的访问都会调用用户定义的__getattribute__()方法,性能上会有所损耗,比使用默认的方法要慢。

2)覆盖的__getattr__()方法如果能够动态处理事先未定义的属性,可以更好地实现数据隐藏。因为dir()通常只显示正常的属性和方法,因此不会将该属性列为可用属性,上述例子中如果动态添加属性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__','__subc
lasshook__', '__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

当实例化a1时由于其默认的属性x为None,当我们访问a1.a时,最先搜索的是__getattribute__()方法,由于a是一个property对象,并不存在于a1的dict中,因此并不能返回该方法,此时会搜索property中定义的get()方法,所以返回的结果是a。当用property中的set()方法对x进行修改并再次访问property的get()方法时会抛出异常,这种情况下会触发对__getattr__()方法的调用并返回结果b。程序最后访问类变量输出test是为了说明对类变量的访问不会涉及__getattribute__()和__getattr__()方法。

注意

__getattribute__()总会被调用,而__getattr__()只有在__getattribute__()中引发异常的情况下才会被调用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文