- 译者序
- 前言
- 第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 各部分练习题的解答
- 作者介绍
- 封面介绍
拦截内置操作属性
例如,针对__str__、__add__和__getitem__方法的属性获取分别通过打印、+表达式和索引隐式地运行,而不会指向Python 3.0中的类属性拦截方法。特别是:
·在Python 3.0中,__getattr__和__getattribute__都不会针对这样的属性而运行。
·在Python 2.6中,如果属性在类中未定义的话,__getattr__会针对这样的属性运行。
·在Python 2.6中,__getattribute__只对于新式类可用,并且在Python 3.0中也可以使用。
换句话说,在Python 3.0的类中(以及Python 2.6的新式类中),没有直接的方法来通用地拦截像打印和加法这样的内置操作。在Python 2.X中,这样的操作调用的方法在运行时从实例中查找,就像所有其他属性一样;在Python 3.0中,这样的方法在类中查找。
这种修改使得基于委托的编码模式在Python 3.0中更为复杂,因为它们不能通用地拦截操作符重载方法调用并将它们指向一个嵌入的对象。这不是一个严重的错误——包装类可以通过在自身中重新定义所有相关的操作符重载方法,从而委托调用以解决这一约束。这些额外的方法可以手动添加,用工具添加,或者通过在共同超类中定义并从共同超类继承。然而,相对于操作符重载方法是被包装对象接口的一部分的情况,这种方法确实使包装更有用处。
记住,这个问题只适用于__getattr__和__getattribute__。由于特性和描述符只针对特定属性定义,所以它们根本不能真正应用于基于代理的类——单个特性或描述符不能用于拦截任意属性。此外,定义操作符重载方法和属性拦截的一个类将能够正确地工作,而不管定义的属性拦截的类型。我们在这里只是关心没有定义操作符重载方法但是力图通用地拦截它们的类。
考虑如下的例子,即文件getattr.py,它在包含了__getattr__和__getattribute__方法的类的实例上,测试各种属性类型和内置操作:
在Python 2.6下运行的时候,__getattr__的确接收针对内置操作的各种隐式属性获取,因为Python通常在实例中查询这样的属性。相反,对于任何操作符重载名,__getattribute__不会运行,因为这样的名称只在类中查找:
注意,在Python 2.6中,这里的__getattr__如何拦截__call__和__str__的隐式和显式获取。相反,对于内置操作的任何属性名,__getattribute__不能捕捉隐式获取。
确实,__getattribute__例子在Python 2.6中与其在Python 3.0中是相同的,因为在Python 2.6中类必须通过派生自object成为新式类,才能使用这个方法。这段代码的object派生在Python 3.0中是可选的,因为其中所有的类都是新式的。
然而,在Python 3.0下运行的时候,__getattr__的结果不同——当通过内置操作获取属性的时候,没有隐式运行的操作符重载方法会触发哪个属性拦截方法。在解析这样的名称的时候,Python 3.0省略了常规实例查找机制:
我们可以跟踪这些输出,从而了解到脚本中的打印,看看这是如何工作的:
·在Python 3.0中,__str__访问有两次未能被__getattr__捕获:一次是针对内置打印,一次是针对显式获取,因为从该类继承了一个默认方法(实际上,该类来自内置object,它是每个类的一个超类)。
·__str__只有一次未能被__getattribute__捕获,在内置打印操作中,显式获取绕过了继承的版本。
·__call__在Python 3.0中用于内置调用表达式的两次都没有捕获,但是,当显式获取的时候,它两次都拦截到了;和__str__不同,没有继承的__call__默认版本能够超越__getattr__。
·__len__被两个类都捕获了,直接原因是,它在类自身中是一个显式定义的方法——它的名称指明了,在Python 3.0中,如果我们删除了类的__len__方法,它不会指向__getattr__或__getattribute__。
·所有其他的内置操作在Python 3.0中都没有被两种方案拦截。
再一次,直接的效果是,由内置操作隐式地运行的操作符重载方法始终不会通过Python 3.0中的某个属性拦截方法指向:Python 3.0在类中查找这样的属性,并且完全忽略了实例查找。
这使得基于委托的包装类在Python 3.0中更难以编写,如果被包装的类可能包含操作符重载方法,这些方法必须冗余地在包装类中重新定义,从而能够委托给被包装的对象。在一般的委托工具中,这可能会增加很多额外的方法。
当然,这些方法的增加可能一部分是工具自动进行的,通过用新的方法来扩展类做到(这里,下两章将要介绍的类装饰器和元类可能会有帮助)。此外,超类可能能够一次性定义所有这些额外方法,以便在基于委托的类中继承。然而,在Python 3.0中,委托编码模式需要额外的工作。
要了解关于这一现象的更实际的说明及其解决方法,参见下一章中的Private装饰器示例。在那里,我们将看到,也可能在客户类中插入一个__getattribute__从而保留其最初的类型,尽管这个方法仍然不会为了操作符重载方法而调用;例如,打印仍然直接运行在这样的一个类中定义的__str__,而不是通过__getattribute__指向请求。
作为另一个例子,下一小节重复了我们的类教程示例。既然理解了属性拦截是如何工作的,我们将能够解释稍微奇怪一点的一个例子。
注意:要查看这一Python 3.0改变在Python自身中工作的一个例子,参见本书第14章中Python 3.0的os.popen对象的介绍。由于它用一个包装类实现,而该包装类使用__getattr__把属性获取委托给一个嵌入的对象,它没有拦截Python 3.0中的next(X)内置迭代器函数,该函数定义为运行__next__。然而,它确实拦截并委托显式X.__next__()调用,因为这些不是通过内置函数指向的,并且没有像__str__那样继承自一个超类。
这等同于我们的例子中的__call__——对内置函数的隐式调用不会触发__getattr__,但对于不是继承自类类型的显式调用,则会触发__getattr__。换句话说,这一改变不仅影响到我们的委托者,而且会影响到Python标准库中的那些类!既然这一修改有了一定的范围,这些行为未来可能会发展,因此,确保在以后的版本中验证这个问题。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论