返回介绍

拦截内置操作属性

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

在介绍__getattr__和__getattribute__的时候,我说它们分别拦截未定义的以及所有的属性获取,这使得它们很适合用于基于委托的编码模式。尽管对于常规命名的属性来说是这样,但它们的行为需要一些额外的澄清:对于隐式地使用内置操作获取的方法名属性,这些方法可能根本不会运行。这意味着操作符重载方法调用不能委托给被包装的对象,除非包装类自己重新定义这些方法。

例如,针对__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 技术交流群。

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

发布评论

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