【问题标题】:How can we consistently override/overload python's dot operator, `__getattrribute__`?我们如何才能始终如一地覆盖/重载 python 的点运算符,`__getattrribute__`?
【发布时间】:2021-04-09 05:13:54
【问题描述】:

假设我们使用的是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__
以下等价于x = robby.length

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

但是,点运算符始终实现为__getattribute__

Python 有“魔术方法
魔术方法是名称以两个下划线字符开头和结尾的任何方法。
__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 的子类。
然后我尝试在Rectangle 类中覆盖object.__getattribute__,通常会失败。

以下显示了一个类的示例,其中 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 的点运算符。
什么是可读、干净且一致的一种方法示例?

一致,我的意思是,只要在源代码中使用.,我们的自定义点运算符就会被调用,无论该方法是否是魔术方法.

我不愿意在阳光下手动输入每一个魔法方法。
我不想看到成千上万行代码看起来像:

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

我了解__getattr____getattribute__ 之间的区别
覆盖__getattribute__ 而不是__getattr__不是当前的问题。

【问题讨论】:

标签: python python-3.x operator-overloading getattribute dot-operator


【解决方案1】:

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


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

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

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

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

【讨论】:

  • 如果我们定义一个名为 sypetype 的子类会怎样。 sype 将覆盖type 类中定义的默认__mro__(方法解析顺序)。如果我从sype 实例化一个名为klass 的类,那么魔法方法可能会使用__getattribute__
  • @SamuelMuldoon:我不确定这个想法应该是什么,但它doesn't work。使用类的 MRO 不会改变特殊方法查找的工作方式。 MRO 搜索将通过您设置的任何奇怪的 MRO,但它仍然是直接的 MRO 搜索。 (我重写了计算 MRO 的 mro 方法,而不是 __mro__ 描述符,因为尝试重写 __mro__ 甚至不会影响实际的 MRO。)