返回介绍

开放问题

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

还是老样子,这个示例设计为在Python 2.6和Python 3.0下都能工作(提供了运算符重载方法,以便在包装器中重定义委托)。然而,和大多数软件一样,总是有改进的地方。

缺陷:运算符重载方法无法在Python 3.0下委托

就像使用__getattr__的所有的基于委托的类,这个装饰器只对常规命名的属性能够跨版本工作。像__str__和__add__这样在新式类下不同工作的运算符方法,在Python 3.0下运行的时候,如果定义了嵌入的对象,将无法有效到达。

正如我们在上一章所了解到的,传统类通常在运行时在实例中查找运算符重载名称,但新式类不这么做——它们完全略过实例,在类中查找这样的方法。因此,在Python 2.6的新式类和Python 3.0的所有类中,__X__运算符重载方法显式地针对内置操作运行,不会触发__getattr__和__getattribute__。这样的属性获取将会和我们的onInstance.__getattr__一起忽略,因此,它们无法验证或委托。

我们的装饰器类没有编写为新式类(通过派生自object),因此,如果在Python 2.6下运行,它将会捕获运算符重载方法。由于在Python 3.0下所有的类自动都是新式类,如果它们在嵌入的对象上编码,这样的方法将会失效。Python 3.0中最简单的解决方案是,在onInstance中重新冗余地定义所有那些可能在包装的对象中用到的运算符重载方法。例如,可以手动添加额外的方法,可以通过工具来自动完成部分任务(例如,使用类装饰器或者下一章将要介绍的元类),或者通过在超类中定义。

要亲自看到不同,可尝试在Python 2.6下对使用运算符重载方法的一个类使用该装饰器。验证与前面一样有效,但是打印所使用的__str__方法和为+而运行的__add__方法二者都会调用装饰器的__getattr__,并由此最终将验证并正确地委托给主体Person对象:

同样的代码在Python 3.0下运行的时候,显式地调用__str__和__add__将会忽略装饰器的__getattr__,并且在装饰器类之中或其上查找定义;print最终查找到从类类型继承的默认显示(从技术上讲,是从Python 3.0中隐藏的object超类),并且+产生一个错误,因为没有默认继承:

使用替代的__getattribute__方法在这里帮不上忙——尽管它定义为捕获每次属性引用(而不只是未定义的名称),它也不会由内置操作运行。我们在本书第37章介绍的Python的特性功能,在这里也帮不上忙。回忆一下,特性自动运行与在编写类的时候定义的特定属性相关的代码,并且不会设计来处理包装对象中的任意属性。

正如前面所提到的,Python 3.0中最直接的解决方案是:在类似装饰器的基于委托的类中,冗余地重新定义可能在嵌入对象中出现的运算符重载名称。这种方法并不理想,因为它产生了一些代码冗余,特别是与Python 2.6的解决方案相比较尤其如此。然而,这不会有太大的编码工作,在某种程度上可以使用工具或超类来自动完成,足以使装饰器在Python 3.0下工作,并且也允许运算符重载名称声明为Private或Public(假设每个运算符重载方法内部都运行failIf测试):

添加了这样的运算符重载方法,前面带有__str__和__add__的示例在Python 2.6和Python 3.0下都能同样地工作,尽管在Python 3.0下可能需要增加大量的额外代码——从原则上讲,每个不会自动运行的运算符重载方法,都需要在这样的一个通用工具类中针对Python 3.0冗余地定义(这就是为什么我们的代码省略这一扩展)。由于在Python 3.0中每个类都是新式的,所以在这一版本中,基于委托的代码更加困难(尽管不是没有可能)。

另一方面,委托包装器可以直接从一个曾经重定义了运算符重载方法的公共超类继承,使用标准的委托代码。此外,像额外的类装饰器或元类这样的工具,可能会自动地向委托类添加这样的方法,从而使一部分工作自动化(参见第39章中类扩展的示例以了解更多信息)。尽管还是不像Python 2.6中的解决方案那样简单,这样的技术能够帮助Python 3.0的委托类更加通用。

实现替代:__getattribute__插入,调用堆栈检查

尽管在包装器中冗余地定义运算符重载方法可能是前面介绍的Python 3.0难题的最直接解决方案,但它不是唯一的方法。我们没有足够篇幅来更深入地介绍这一问题,因此,研究其他潜在的解决方案就放在了一个建议的练习中。由于一个棘手的替代方案非常强调类概念,因此这里简单提及一下其优点。

这个示例的一个缺点是,实例对象并不真的是最初的类的实例——它们是包装器的实例。在某些依赖于类型测试的程序中,这可能就有麻烦。为了支持这样的类,我们可能试图通过在最初类中插入一个__getattribute__方法来实现类似的效果,以捕获在其实例上的每次属性引用。插入的方法将会把有效的请求向上传递到其超类以避免循环,使用我们在前面一章学习过的技术。如下是对类装饰器代码的潜在修改:

这一替代方案解决了类型测试的问题,但是带来了其他的问题。例如,它只处理属性获取——也就是,这个版本允许自由地对私有名称赋值。拦截赋值仍然必须使用__setattr__,或者是一个实例包装器对象,或者插入另一个类方法。添加一个实例包装器来捕获赋值可能会再次改变类型,并且如果最初的类使用自己的__setattr__(或者一个__getattribute__,对前面的情况),插入方法将会失效。一个插入的__setattr__还必须考虑到客户类中的一个__slots__。

此外,这种方法解决了上一小节介绍的内置操作属性问题,因为在这些情况下__getattribute__并不运行。在我们的例子中,如果Person有一个__str__,打印操作将运行它,但只是因为它真正出现在了那个类中。和前面一样,__str__属性不会一般性地路由到插入的__getattribute__方法——打印将会绕过这个方法,并且直接调用类的__str__。

尽管这可能比在包装对象内根本不支持运算符重载方法要好(除非重定义),但这种方法仍然没有拦截和验证__X__方法,这使得它们不可能成为Private的。尽管大多数运算符重载方法意味着是公有的,但有一些可能不是。

更糟糕的是,由于这个非包装器方法通过向装饰类添加一个__getattribute__来工作,它也会拦截类自身做出的属性访问,并像对来自类外部的访问一样地验证它们——这也意味着类的方法不能够使用Private名称!

实际上,像这样插入方法功能上等同于继承它们,并且意味着与我们在第29章最初的私有代码同样的限制。要知道一个属性访问是源自于类的内部还是外部,我们的方法需要在Python调用堆栈上检查frame对象。这可能最终产生一个解决方案(例如,使用检查堆栈的特性或描述符来替代私有属性),但是它可能会进一步减慢访问,并且其内部对我们来说过于复杂,无法在此介绍。

尽管有趣并且可能与一些其他的使用情况相关,但这种插入技术的方法并没有达到我们的目标。这里,我们不会进一步介绍这一选项的编码模式,因为我们将在下一章中学习类扩展技术,与元类联合使用。正如我们将在那里看到的,元类并不严格需要以这种方式修改类,因为类装饰器往往充当同样的角色。

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

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

发布评论

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