我们如何始终覆盖/重载 python 的点运算符,`__getattrribute__`?

问题描述

假设我们使用的是 python 3.x 或更新版本,而不是 python 2.x

Python 和许多语言一样,有一个点运算符:

# Create a new instance of the Rectangle class
robby = Rectangle(3,10)

# INVOKE THE DOT OPERATOR       
x = robby.length 

Python 的点运算符有时实现为 __getattribute__。@H_502_19@ 以下等效于 x = robby.length

x = Rectangle.__getattribute__(robby,"length")    

但是,点运算符总是实现为 __getattribute__

Python 有“魔法方法”@H_502_19@魔术方法名称以两个下划线字符开头和结尾的任何方法。@H_502_19@__len__() 是魔法方法一个例子。

你可以通过执行下面的代码得到python的大部分魔术方法的列表:

print("\n".join(filter(lambda s: s.startswith("__"),dir(int))))

输出为:

__abs__
__add__
__and__
__bool__
__ceil__
__class__
__delattr__
__dir__
__divmod__
__doc__
__eq__
__float__
[... truncated / abridged ...]
__rtruediv__
__rxor__
__setattr__
__sizeof__
__str__
__sub__
__subclasshook__
__truediv__
__trunc__
__xor__

假设我们写了一个名为 Rectangle 的类,object 的子类。@H_502_19@ 然后我尝试在 object.__getattribute__ 类中覆盖 Rectangle,通常会失败。

以下显示一个类的示例,其中 python 有时会忽略重写的点运算符:

class Klass:
    def __getattribute__(self,attr_name):
        return print

obj = Klass()

obj.append()  # WORKS FINE. `obj.append == print`
obj.delete()  # WORKS FINE. `obj.delete == print`
obj.length    # WORKS FINE
obj.x         # WORKS FINE

# None of the following work,because they
# invoke magic methods.  

# The following line is similar to:
#     x = Klass.__len__(obj)
len(obj)

# obj + 5
#     is similar to:
# x = Klass.__add__(obj,5)
x = obj + 5

# The following line is similair to:
#     x = Klass.__radd__(obj,2)
x = 2 + obj

有不止一种方法可以覆盖python的点运算符。@H_502_19@ 什么是可读、干净和一致方法的例子?

通过一致,我的意思是每当在源代码中使用 . 时,我们的自定义点运算符都会被调用,无论该方法是否是魔术方法与否。

我不愿意在阳光下手动输入每一个神奇的方法。@H_502_19@ 我不想看到像这样的数千行代码

def __len__(*args,**kwargs):
    return getattr(args[0],"__len__")(*args,**kwargs)

我了解 __getattr____getattribute__@H_502_19@ 之间的区别 覆盖 __getattribute__ 而不是 __getattr__手头的问题。

解决方法

__getattribute__ 已经完成了您真正要求的操作 - 覆盖 __getattribute__ 是您处理 . 运算符的所有用法所需的全部内容。 (严格来说,如果 __getattr__ 失败,Python 将回退到 __getattribute__,但只要不实现 __getattr__,您就不必担心。)


您说您希望“每当 . 在源代码中使用时”调用您的运算符,但是 len+ 以及您担心的所有其他事情 不要使用 ..len(obj)obj + 5 中没有 2 + obj

大多数魔术方法查找不使用属性访问。如果您实际查找 yourobj.__len__yourobj.__add__,将通过属性访问,并且您的 __getattribute__ 将被调用,但是当 Python 查找实现语言功能的魔术方法时,它会直接搜索对象类型的 MRO。不涉及 . 运算符。

没有办法覆盖魔术方法查找。这是一个没有覆盖钩子的硬编码过程。您可以做的最接近的事情是覆盖单个魔术方法以委托给 __getattribute__,但这与覆盖魔术方法查找(或覆盖 .)不同,并且很容易获得无限递归错误方式。

如果您真正想要做的只是避免重复的单个魔术方法覆盖,您可以将它们放在类装饰器或 mixin 中。