- Preface 前言
- 第1章 引论
- 第2章 编程惯用法
- 第3章 基础语法
- 建议19:有节制地使用 from…import 语句
- 建议20:优先使用 absolute import 来导入模块
- 建议21:i+=1 不等于 ++i
- 建议22:使用 with 自动关闭资源
- 建议23:使用 else 子句简化循环(异常处理)
- 建议24:遵循异常处理的几点基本原则
- 建议25:避免 finally 中可能发生的陷阱
- 建议26:深入理解 None 正确判断对象是否为空
- 建议27:连接字符串应优先使用 join 而不是 +
- 建议28:格式化字符串时尽量使用 .format 方式而不是 %
- 建议29:区别对待可变对象和不可变对象
- 建议30:[]、() 和 {}:一致的容器初始化形式
- 建议31:记住函数传参既不是传值也不是传引用
- 建议32:警惕默认参数潜在的问题
- 建议33:慎用变长参数
- 建议34:深入理解 str() 和 repr() 的区别
- 建议35:分清 staticmethod 和 classmethod 的适用场景
- 第4章 库
- 建议36:掌握字符串的基本用法
- 建议37:按需选择 sort() 或者 sorted()
- 建议38:使用 copy 模块深拷贝对象
- 建议39:使用 Counter 进行计数统计
- 建议40:深入掌握 ConfigParser
- 建议41:使用 argparse 处理命令行参数
- 建议42:使用 pandas 处理大型 CSV 文件
- 建议43:一般情况使用 ElementTree 解析 XML
- 建议44:理解模块 pickle 优劣
- 建议45:序列化的另一个不错的选择 JSON
- 建议46:使用 traceback 获取栈信息
- 建议47:使用 logging 记录日志信息
- 建议48:使用 threading 模块编写多线程程序
- 建议49:使用 Queue 使多线程编程更安全
- 第5章 设计模式
- 第6章 内部机制
- 建议54:理解 built-in objects
- 建议55:init() 不是构造方法
- 建议56:理解名字查找机制
- 建议57:为什么需要 self 参数
- 建议58:理解 MRO 与多继承
- 建议59:理解描述符机制
- 建议60:区别 getattr() 和 getattribute() 方法
- 建议61:使用更为安全的 property
- 建议62:掌握 metaclass
- 建议63:熟悉 Python 对象协议
- 建议64:利用操作符重载实现中缀语法
- 建议65:熟悉 Python 的迭代器协议
- 建议66:熟悉 Python 的生成器
- 建议67:基于生成器的协程及 greenlet
- 建议68:理解 GIL 的局限性
- 建议69:对象的管理与垃圾回收
- 第7章 使用工具辅助项目开发
- 第8章 性能剖析与优化
建议60:区别 __getattr__() 和 __getattribute__() 方法
__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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论