- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
__getattr__ 和 __getattribute__
属性获取拦截表现为两种形式,可用两个不同的方法来编写:
·__getattr__针对未定义的属性运行——也就是说,属性没有存储在实例上,或者没有从其类之一继承。
·__getattribute__针对每个属性,因此,当使用它的时候,必须小心避免通过把属性访问传递给超类而导致递归循环。
我们在本书第29章中遇到过前一种情况,它在Python的所有版本中都可用。后者对于Python 2.6中的新式类可用,并且对于Python 3.0中的所有类(隐式都是新式类)可用。这两个方法是一组属性拦截方法的代表,这些方法还包括__setattr__和__delattr__。由于这些方法具有相同的作用,我们在这里将它们通常作为一个单独话题。
与特性和描述符不同,这些方法是Python的操作符重载协议的一部分——是类的特殊命名的方法,由子类继承,并且当在隐式的内置操作中使用实例的时候自动调用。和一个类的所有方法一样,它们每一个在调用的时候都接收第一个self参数,访问任何请求的实例状态信息或该类的其他方法。
__getattr__和__getattribute__方法也比特性和描述符更加通用——它们可以用来拦截对任何(几乎所有的)实例属性的获取,而不仅仅只是分配给它们的那些特定名称。因此,这两个方法很适合于通用的基于委托的编码模式——它们可以用来实现包装对象,该对象管理对一个嵌套对象的所有属性访问。相反,我们必须为想要拦截的每个属性都定义一个特性或描述符。
最后,这两种方法比我们前面考虑的替代方法的应用领域更专注集中一些:它们只是拦截属性获取,而不拦截属性赋值。要捕获赋值对属性的更改,我们必须编写一个__setattr__方法——这是一个操作符重载方法,只对每个属性获取运行,必须小心避免由于通过实例命名空间字典指向属性赋值而导致的递归循环。
尽管很少用到,我们还是可以编写一个__delattr__重载方法(必须以同样的方式避免循环)来拦截属性删除。相反,特性和描述符通过设计捕获访问、设置和删除操作。
大多数这些操作符重载方法在本书前面都介绍过,这里,我们将展开讨论其用法并研究它们在更大的应用环境中的作用。
基础知识
__getattr__和__setattr__在本书第29章和第31章介绍,并且第31章简单地提到了__getattribute__。简而言之,如果一个类定义了或继承了如下方法,那么当一个实例用于后面的注释所提到的情况时,它们将自动运行:
所有这些之中,self通常是主体实例对象,name是将要访问的属性的字符串名,value是要赋给该属性的对象。两个get方法通常返回一个属性的值,另两个方法不返回什么(None)。例如,要捕获每个属性获取,我们可以使用上面的前两个方法;要捕获属性赋值,可以使用第三个方法:
这样的代码结构可以用来实现我们在第30章介绍的委托设计模式。由于所有的属性通常都指向我们的拦截方法,所以我们可以验证它们并将其传递到嵌入的、管理的对象中。例如,下面的类(取自第30章)跟踪了对传递给包装类的另一个对象的每一次属性获取:
特性和描述符没有这样的类似功能,做不到对每个可能的包装对象中每个可能的属性编写访问器。
避免属性拦截方法中的循环
这些方法通常都容易使用,它们唯一复杂的部分就是潜在的循环(即递归)。由于__getattr__仅针对未定义的属性调用,所以它可以在自己的代码中自由地获取其他属性。然而,由于__getattribute__和__setattr__针对所有的属性运行,因此,它们的代码要注意在访问其他属性的时候避免再次调用自己并触发一次递归循环。
例如,在一个__getattribute__方法代码内部的另一次属性获取,将会再次触发__getattribute__,并且代码将会循环直到内存耗尽:
要解决这个问题,把获取指向一个更高的超类,而不是跳过这个层级的版本——object类总是一个超类,并且它在这里可以很好地起作用:
对于__setattr__,情况是类似的。在这个方法内赋值任何属性,都会再次触发__setattr__并创建一个类似的循环:
要解决这个问题,把属性作为实例的__dict__命名空间字典中的一个键赋值。这样就避免了直接的属性赋值:
尽管这种方法比较少用到,但__setattr__也可以把自己的属性赋值传递给一个更高的超类而避免循环,就像__getattribute__一样:
相反,我们不能使用__dict__技巧在__getattribute__中避免循环:
获取__dict__属性本身会再次触发__getattribute__,导致一个递归循环。很奇怪,但确实如此。
__delattr__方法实际中很少用到,但是,当用到的时候,它针对每次属性删除而调用(就像针对每次属性赋值调用__setattr__一样)。因此,我们必须小心,在删除属性的时候要避免循环,通过使用同样的技术:命名空间字典或者超类方法调用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论