返回介绍

__getattr__ 和 __getattribute__

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

到目前为止,我们已经学习了特性和描述符——管理特定属性的工具。__getattr__和__getattribute__操作符重载方法提供了拦截类实例的属性获取的另一种方法。就像特性和描述符一样,它们也允许我们插入当访问属性的时候自动运行的代码。然而,我们将会看到,这两个方法有更广泛的应用。

属性获取拦截表现为两种形式,可用两个不同的方法来编写:

·__getattr__针对未定义的属性运行——也就是说,属性没有存储在实例上,或者没有从其类之一继承。

·__getattribute__针对每个属性,因此,当使用它的时候,必须小心避免通过把属性访问传递给超类而导致递归循环。

我们在本书第29章中遇到过前一种情况,它在Python的所有版本中都可用。后者对于Python 2.6中的新式类可用,并且对于Python 3.0中的所有类(隐式都是新式类)可用。这两个方法是一组属性拦截方法的代表,这些方法还包括__setattr__和__delattr__。由于这些方法具有相同的作用,我们在这里将它们通常作为一个单独话题。

与特性和描述符不同,这些方法是Python的操作符重载协议的一部分——是类的特殊命名的方法,由子类继承,并且当在隐式的内置操作中使用实例的时候自动调用。和一个类的所有方法一样,它们每一个在调用的时候都接收第一个self参数,访问任何请求的实例状态信息或该类的其他方法。

__getattr__和__getattribute__方法也比特性和描述符更加通用——它们可以用来拦截对任何(几乎所有的)实例属性的获取,而不仅仅只是分配给它们的那些特定名称。因此,这两个方法很适合于通用的基于委托的编码模式——它们可以用来实现包装对象,该对象管理对一个嵌套对象的所有属性访问。相反,我们必须为想要拦截的每个属性都定义一个特性或描述符。

最后,这两种方法比我们前面考虑的替代方法的应用领域更专注集中一些:它们只是拦截属性获取,而不拦截属性赋值。要捕获赋值对属性的更改,我们必须编写一个__setattr__方法——这是一个操作符重载方法,只对每个属性获取运行,必须小心避免由于通过实例命名空间字典指向属性赋值而导致的递归循环。

尽管很少用到,我们还是可以编写一个__delattr__重载方法(必须以同样的方式避免循环)来拦截属性删除。相反,特性和描述符通过设计捕获访问、设置和删除操作。

大多数这些操作符重载方法在本书前面都介绍过,这里,我们将展开讨论其用法并研究它们在更大的应用环境中的作用。

基础知识

__getattr__和__setattr__在本书第29章和第31章介绍,并且第31章简单地提到了__getattribute__。简而言之,如果一个类定义了或继承了如下方法,那么当一个实例用于后面的注释所提到的情况时,它们将自动运行:

所有这些之中,self通常是主体实例对象,name是将要访问的属性的字符串名,value是要赋给该属性的对象。两个get方法通常返回一个属性的值,另两个方法不返回什么(None)。例如,要捕获每个属性获取,我们可以使用上面的前两个方法;要捕获属性赋值,可以使用第三个方法:

这样的代码结构可以用来实现我们在第30章介绍的委托设计模式。由于所有的属性通常都指向我们的拦截方法,所以我们可以验证它们并将其传递到嵌入的、管理的对象中。例如,下面的类(取自第30章)跟踪了对传递给包装类的另一个对象的每一次属性获取:

特性和描述符没有这样的类似功能,做不到对每个可能的包装对象中每个可能的属性编写访问器。

避免属性拦截方法中的循环

这些方法通常都容易使用,它们唯一复杂的部分就是潜在的循环(即递归)。由于__getattr__仅针对未定义的属性调用,所以它可以在自己的代码中自由地获取其他属性。然而,由于__getattribute__和__setattr__针对所有的属性运行,因此,它们的代码要注意在访问其他属性的时候避免再次调用自己并触发一次递归循环。

例如,在一个__getattribute__方法代码内部的另一次属性获取,将会再次触发__getattribute__,并且代码将会循环直到内存耗尽:

要解决这个问题,把获取指向一个更高的超类,而不是跳过这个层级的版本——object类总是一个超类,并且它在这里可以很好地起作用:

对于__setattr__,情况是类似的。在这个方法内赋值任何属性,都会再次触发__setattr__并创建一个类似的循环:

要解决这个问题,把属性作为实例的__dict__命名空间字典中的一个键赋值。这样就避免了直接的属性赋值:

尽管这种方法比较少用到,但__setattr__也可以把自己的属性赋值传递给一个更高的超类而避免循环,就像__getattribute__一样:

相反,我们不能使用__dict__技巧在__getattribute__中避免循环:

获取__dict__属性本身会再次触发__getattribute__,导致一个递归循环。很奇怪,但确实如此。

__delattr__方法实际中很少用到,但是,当用到的时候,它针对每次属性删除而调用(就像针对每次属性赋值调用__setattr__一样)。因此,我们必须小心,在删除属性的时候要避免循环,通过使用同样的技术:命名空间字典或者超类方法调用。

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

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

发布评论

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