为何大于和不等于运算符在只重载小于和等于运算符时仍然有效?

1 投票
2 回答
953 浏览
提问于 2025-05-10 22:58

我现在在研究Python是如何实现运算符重载的。到目前为止,我发现Python的方式比C++更吸引人,尤其是在处理像*(或者其他类似的算术运算符)时,因为这些运算符可以从右到左(2*x)和从左到右(x*2)进行操作。

我有一个测试用的类:

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))

我在我的自定义类中不断添加更多的运算符,并观察它们的表现。你可能注意到了两个事情(根据我标题中的问题):

  • __gt__没有被重载,
  • Vector3.compareAndPrint(...)函数使用了>(大于)运算符。

出于某种原因,我得到了我预期的输出,就好像我重载了>一样:

[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是反转了重载的==运算符。

相关文章:

  • 暂无相关问题
暂无标签

2 个回答

2

这是关于Python数据模型的一些内容,很多人似乎不太理解。要想弄明白这些,我们需要从二元算术操作开始,比如乘法(__mul__)、加法(__add__)等等。

我们注意到有一个 __mul__ 方法和一个 __rmul__ 方法。它们的区别在文档中有说明,特别是在后者的部分:

这些方法用于实现二元算术操作(比如 +, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |),当操作数的顺序被反转时会调用这些方法。只有当左边的操作数不支持对应的操作,并且操作数类型不同的时候,这些函数才会被调用。

接下来,当我们查看丰富比较方法的文档时:

这些方法没有交换参数的版本(用于左边的参数不支持操作但右边的参数支持的情况);相反,__lt__()__gt__() 是彼此的反射。

所以,在你的情况中,由于 __gt__ 没有被重载,Python实际上会交换参数的顺序并调用 __lt__。这真是挺有意思的。


顺便说一下,如果你想构建一个可以与其他同类实例进行排序的类,使用functools.total_ordering 装饰器会非常有帮助。你只需要提供 __lt____eq__,然后这个装饰器会帮你处理其他的。

2

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

对于比较操作,__gt____rgt__这两个方法其实有点不同,__rgt__实际上就是__lt__。这意味着当你写left_thing > right_thing时,如果left_thing不知道该怎么做,Python会尝试用right_thing < left_thing来解决。只要你实现了__lt__,这个方法就能正常工作。

需要注意的是,如果__gt____lt__都失败了,Python不会尝试使用__le____ge____eq__这些方法。

撰写回答