返回介绍

状态信息保持选项

发布于 2024-01-29 22:24:14 字数 2688 浏览 0 评论 0 收藏 0

前面小节的最后一个例子引发了一个重要的问题。函数装饰器有各种选项来保持装饰的时候所提供的状态信息,以便在实际函数调用过程中使用。它们通常需要支持多个装饰的对象以及多个调用,但是,有多种方法来实现这些目标:实例属性、全局变量、非局部变量和函数属性,都可以用于保持状态。

类实例属性

例如,这里是前面的例子的一个扩展版本,其中添加了对关键字参数的支持,并且返回包装函数的结果,以支持更多的用例:

就像最初的版本一样,这里的代码使用类实例属性来显式地保存状态。包装的函数和调用计数器都是针对每个实例的信息——每个装饰都有自己的拷贝。当在Python 2.6和Python 3.0下运行一段脚本的时候,这个版本的输出如下所示。注意spam和eggs函数的每一个是如何有自己的调用计数器的,因为每个装饰都创建一个新的类实例:

尽管对于装饰函数有用,但是当应用于方法的时候,这种编码方案也有问题(随后更为详细地介绍)。

封闭作用域和全局作用域

封闭def作用域引用和嵌套的def常常可以实现相同的效果,特别是对于装饰的最初函数这样的静态数据。然而,在这个例子中,我们也需要封闭的作用域中的一个计数器,它随着每次调用而更改,并且,这在Python 2.6中是不可能的。在Python 2.6中,我们可以使用类和属性,正如我们前面所做的那样,或者使用全局声明把状态变量移出到全局作用域:

遗憾的是,把计数器移出到共同的全局作用域允许像这样修改它们,也意味着它们将为每个包装的函数所共享。和类实例属性不同,全局计数器是跨程序的,而不是针对每个函数的——对于任何跟踪的函数调用,计数器都会递增。如果你比较这个版本与前一个版本的输出,就可以看出其中的区别——单个的、共享的全局调用计数器根据每次装饰函数的调用不正确地更新:

封闭作用域和nonlocal

共享全局状态可能是我们在某些情况下想要的。如果我们真的想要一个针对每个函数的计数器,要么像前面那样使用类,要么使用Python 3.0中新的nonlocal语句,第17章曾介绍过该语句。由于这一新的语句允许修改封闭的函数作用域变量,所以它们可以充当针对每次装饰的、可修改的数据:

现在,由于封装的作用域变量不能跨程序而成为全局的,所以每个包装的函数再次有了自己的计数器,就像是针对类和属性一样。这里是在Python 3.0下运行时新的输出:

函数属性

最后,如果你没有使用Python 3.X并且没有一条nonlocal语句,可能仍然能够针对某些可改变的状态使用函数属性来避免全局和类。在最新的Pythons中,我们可以把任意属性分配给函数以附加它们,使用func.attr=value就可以了。在我们的例子中,可以直接对状态使用wrapper.calls。如下的代码与前面的nonlocal版本一样地工作,因为计数器再一次是针对每个装饰的函数的,但是,它也可以在Python 2.6下运行:

注意,这种方法有效,只是因为名称wrapper保持在封闭的tracer函数的作用域中。当我们随后增加wrapper.calls时,并不是在修改名称wrapper本身,因此,不需要nonlocal声明。

这种方案几乎作为一个脚注来介绍,因为它比Python 3.0中的nonlocal要隐晦得多,并且可能留待其他方案无济于事的情况下使用更好。然而,我们将解答本章末尾一个问题的时候使用它,那里,我们需要从装饰器代码的外部访问保存的状态;nonlocal只能从嵌套函数自身的内部看到,但是函数属性有更广泛的可见性。

由于装饰器往往意味着可调用对象的多个层级,所以我们可以用封闭的作用域和带有属性的类来组合函数,以实现各种各样的编码结构。正如我们随后将见到的,这有时候可能比我们所期待的要细微——每个装饰的函数应该有自己的状态,并且每个装饰的类都应该需要针对自己的状态和针对每个产生实例的状态。

实际上,正如下一小节所介绍的,如果我们也想要对一个类方法应用函数装饰器,必须小心Python在作为可调用类实例对象的装饰器编码和作为函数的装饰器编码之间的区分。

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

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

发布评论

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