为什么大于和不等于运算符可以工作,即使只有小于和等于运算符被重载

Why does greater than and unequal operators work even though only less than and equal operator has been overloaded

我目前正在研究 Python 如何重载其运算符。到目前为止,我发现它比 C++ 更具吸引力,尤其是在运算符方面,例如 *(或类似的算术运算符)必须处理可以从右到左 (2*x) 和左应用的运算以某种方式向右 (x*2)。

我有以下 class 作为测试:

from math import sqrt

class Vector3:
    def __init__(self, x,y,z):
        self.x = x
        self.y = y
        self.z = z

    def __repr__(self):
        return 'Vector3(x=%d, y=%d, z=%d)' % (self.x, self.y, self.z)

    def __str__(self):
        return '[x: %d, y: %d, z: %d]' % (self.x, self.y, self.z)

    def length(self):
        return sqrt(self.x**2 + self.y**2 + self.z**2)

    def __add__(self, vector):
        return Vector3(self.x + vector.x, self.y + vector.y, self.z + vector.z)

    def __sub__(self, vector):
        return Vector3(self.x - vector.x, self.y - vector.y, self.z - vector.z)

    def __mul__(self, scalar):
        return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)

    __rmul__ = __mul__ # Right multiplication equals left multiplication (if this defers, __rmul__ has to be overwritten and defined manually)

    def __eq__(self, vector):
        return (self.x == vector.x and self.y == vector.y and self.z == vector.z)

    def __lt__(self, vector):
        return self.length() < vector.length()

    @staticmethod
    def compareAndPrint(vector1, vector2):
        if vector1 == vector2: return 'v1 == v2 since len(v1) = %f == %f = len(v2)' % (vector1.length(), vector2.length())
        elif vector1 < vector2: return 'v1 < v2 since len(v1) = %f < %f = len(v2)' % (vector1.length(), vector2.length())
        elif vector1 > vector2: return 'v1 > v2 since len(v1) = %f > %f = len(v2)' % (vector1.length(), vector2.length())

v1 = Vector3(1,2,3)
v2 = Vector3(0,-1,1)
v3 = v1 + v2
v4 = v3 - v1
v5 = v1 * 2
v6 = 2 * v1
print(v1)
print(v2)
print(v3)
print(v4)
print(v5)
print(v6)

print(Vector3.compareAndPrint(v1,v2))
print(Vector3.compareAndPrint(v2,v1))
print(Vector3.compareAndPrint(v1,v1))

我只是在我的自定义 class 中添加越来越多的运算符并观察它们的行为方式。您可能已经注意到两件事(根据我在标题中的问题):

出于某种原因,我得到了我期望的输出,就好像我已经超载了 >:

[x: 1, y: 2, z: 3]
[x: 0, y: -1, z: 1]
[x: 1, y: 1, z: 4]
[x: 0, y: -1, z: 1]
[x: 2, y: 4, z: 6]
[x: 2, y: 4, z: 6]
v1 > v2 since len(v1) = 3.741657 > 1.414214 = len(v2)
v1 < v2 since len(v1) = 1.414214 < 3.741657 = len(v2)
v1 == v2 since len(v1) = 3.741657 == 3.741657 = len(v2)

Python 是自动处理这个问题还是我做了一些我没有注意到的事情来完成这个工作?我唯一想到的是 Python 采用 < 的倒数,同时为 == 添加排除项,因为 > 的倒数是 <= 而不仅仅是 <.

同样的事情也适用于 !=(不等于)运算符。在这里,我 99% 确定 Python 会反转重载的 == 运算符。

Python 中的大多数二元运算符都可以被任一操作数重载。左操作数有一种定义方法,如 __add__ 用于加法,右操作数有一种方法,如 __radd__。我记得唯一一个只能被一个操作数重载的是in,右边必须定义

为了比较,__gt____rgt__ 方法,__rgt__ 只是 __lt__。这意味着当你 left_thing > right_thingleft_thing 不知道该做什么时,Python 会尝试 right_thing < left_thing。由于您已经实现了 __lt__,这有效。

请注意,如果 __gt____lt__ 失败,Python 将不会尝试任何涉及 __le____ge____eq__ 的操作。

这是许多人似乎不理解的 python 数据模型的一部分。要通过文档追踪这一点,我们需要从二进制算术运算(__mul____add__ 等)开始。

我们注意到有一个 __mul__ and a __rmul__ method. The difference is described in docs under the latter:

These methods are called to implement the binary arithmetic operations (+, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.

现在,当我们查看 rich comparison methods 的文档时:

There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, __lt__() and __gt__() are each other’s reflection

所以,你的情况是因为 __gt__ 没有被重载,python 实际上交换了参数的顺序并调用了 __lt__。很整洁。


FWIW,如果你想构建一个可与 class 的其他实例一起订购的 class,functools.total_ordering 装饰器可以是 super 有帮助。您只需提供 __lt____eq__,装饰器提供其余部分。