- 译者序
- 前言
- 第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 各部分练习题的解答
- 作者介绍
- 封面介绍
类错误之一:装饰类方法
我在本章前面介绍了这一现象,但是现在,我们可以在真实的工作代码环境中看到它。假设基于类的跟踪装饰器如下:
简单函数的装饰与前面介绍的一样:
然而,类方法的装饰失效了(更明白的读者可能会认识到,这是我们在第27章面向对象教程中的Person类的再现):
这里问题的根源在于,tracer类的__call__方法的self——它是一个tracer实例,还是一个Person实例?我们真的需要将其编写为两者都是:tracer用于装饰器状态,Person用于指向最初的方法。实际上,self必须是tracer对象,以提供对tracer的状态信息的访问;不管装饰一个简单的函数还是一个方法,都是如此。
遗憾的是,当我们用__call__把装饰方法名重绑定到一个类实例对象的时候,Python只向self传递了tracer实例;它根本没有在参数列表中传递Person主体。此外,由于tracer不知道我们要用方法调用处理的Person实例的任何信息,没有办法创建一个带有一个实例的绑定的方法,因此,没有办法正确地分配调用。
实际上,前面的列表最终传递了太少的参数给装饰的方法,并且导致了一个错误。在装饰器的__call__方法添加一行,以打印所有的参数来验证这一点。正如你所看到的,self是一个tracer,并且Person实例完全缺失:
正如前面提到的,出现这种情况是因为:当一个方法名绑定只是绑定到一个简单的函数,Python向self传递了隐含的主体实例;当它是一个可调用类的实例的时候,就传递这个类的实例。从技术上讲,当方法是一个简单函数的时候,Python只是创建了一个绑定的方法对象,其中包含了主体实例。
使用嵌套函数来装饰方法
如果想要函数装饰器在简单函数和类方法上都能工作,最直接的解决方法在于使用前面介绍的状态保持方法之一——把自己的函数装饰器编写为嵌套的def,以便对于包装器类实例和主体类实例都不需要依赖于单个的self实例参数。
如下的替代方案使用Python 3.0的nonlocal。由于装饰的方法重新绑定到简单的函数而不是实例对象,所以Python正确地传递了Person对象作为第一个参数,并且装饰器将其从*args中的第一项传递给真正的、装饰的方法的self参数:
这个版本在函数和方法上都有效:
使用描述符装饰方法
尽管前一小节介绍的嵌套函数的解决方案是支持应用于函数和类方法的装饰器的最直接方法,其他的方法也是可能的。例如,我们在上一章中介绍的描述符功能,在这里也能派上用场。
还记得我们在前一章的讨论中,描述符可能是分配给对象的一个类属性,该对象带有一个__get__方法,当引用或获取该属性的时候自动运行该方法(在Python 2.6中需要对象派生,但在Python 3.0中不需要):
描述符也能够拥有__set__和__del__访问方法,但是,我们在这里不需要它们。现在,由于描述符的__get__方法在调用的时候接收描述符类和主体类实例,因此当我们需要装饰器的状态以及最初的类实例来分派调用的时候,它很适合于装饰方法。考虑如下的替代的跟踪装饰器,它也是一个描述符:
这和前面的嵌套的函数代码一样有效。装饰的函数只调用其__call__,而装饰的方法首先调用其__get__来解析方法名获取(在instance.method上);__get__返回的对象保持主体类实例并且随后调用以完成调用表达式,由此触发__call__。例如,要测试代码的调用:
首先运行tracer.__get__,因为Person类的giveRaise属性已经通过函数装饰器重新绑定到了一个描述符。然后,调用表达式触发返回的包装器对象的__call__方法,它返回来调用tracer.__call__。
包装器对象同时保持描述符和主体实例,因此,它可以将控制指回到最初的装饰器/描述符类实例。实际上,在方法属性获取过程中,包装的对象保持了主体类实例可用,并且将其添加到了随后调用的参数列表,该参数列表会传递给__call__。在这个应用程序中,用这种方法把调用路由到描述符类实例是需要的,由此对包装方法的所有调用都使用描述符实例对象中的同样的调用计数器状态信息。
此外,我们也可以使用一个嵌套的函数和封闭的作用域引用来实现同样的效果——如下的版本和前面的版本一样的有效,通过为一个嵌套函数和作用域引用交换类和对象属性,但是,它所需的代码显著减少:
为这些替代方法添加print语句是为了自己跟踪获取/调用过程的两个步骤,用前面嵌套函数替代方法中同样的测试代码来运行它们。在两种编码中,基于描述符的方法也比嵌套函数的选项要细致得多,因此,它可能是这里的又一种选择。在其他的环境中,它也可能是一种有用的编码模式。
在本章剩余的内容中,我们将相当随意地使用类或函数来编写函数装饰器,只要它们都只适用于函数。一些装饰器可能并不需要最初的类的实例,并且如果编写为一个类,它将在函数和方法上都有效——例如Python自己的静态方法装饰器,就不需要主体类的一个实例(实际上,它主要是从调用中删除实例)。
然而,这里的叙述的教训是,如果你想要装饰器在简单函数和类方法上都有效,最好使用基于嵌套函数的编码模式,而不是带有调用拦截的类。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论