为什么 dunder 方法不一致?

Why are dunder methods inconsistant?

我很困惑。假设我有一个 class(我这样做),其中每个运算符和比较器(+-<>===、等)只是 returns 本身。如果你不明白,这是代码:

class _:
    def __init__(self, *args, **kwargs):
        pass
    def __add__(self, other):
        return self
    def __sub__(self, other):
        return self
    def __mul__(self, other):
        return self
    def __truediv__(self, other):
        return self
    def __floordiv__(self, other):
        return self
    def __call__(self, *args, **kwargs):
        return self
    def __eq__(self, other):
        return self
    def __lt__(self, other):
        return self
    def __gt__(self, other):
        return self
    def __ge__(self, other):
        return self
    def __le__(self, other):
        return self

我发现了一个不一致的地方。以下作品:

_()+_
_()-_
_()*_
_()/_
_()//_
_()>_
_()<_
_()==_
_()>=_
_()<=_
_<_()
_>_()
_==_()
_<=_()
_>=_()

但以下不是:

_+_()
_-_()
_*_()
_/_()
_//_()

他们给出了以下错误:

Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'type' and '_'

总而言之,比较器可以两种方式处理类型和实例,但运算符仅在实例位于运算符左侧时才起作用。这是为什么?

这是因为python会这样翻译源代码:

a + b

进入:

a.__add__(b)

但是:

_ + _()

转换为:

_.__add__(_())

并且 class _ 没有 __add__(),而实例有。

您在这些比较中使用的类型这一事实无关紧要且令人困惑。对于任何未实现运算符的任意对象,您都会看到相同的行为。因此,只需创建另一个 class、class Foo: pass,如果您使用 Foo() 实例,您将看到相同的行为。或者只是一个 object() 实例。

无论如何,算术 dunder 方法都有一个交换参数版本,例如对于 __add__,它是 __radd__(我认为它是“正确添加”)。如果您有 x + y,但未实现 x.__add__,它会尝试使用 y.__radd__

现在,对于比较运算符,没有 __req____rgt__ 运算符。相反,其他运营商自己 这样做。来自 docs:

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, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection.

因此,在您的类型位于左侧的情况下,例如

_<_()

然后 type.__lt__ 不存在,所以它尝试 _.__gt__,它确实存在。

演示:

>>> class Foo:
...     def __lt__(self, other):
...         print("in Foo.__lt__")
...         return self
...     def __gt__(self, other):
...         print("in Foo.__gt__")
...         return self
...
>>> Foo() < Foo
in Foo.__lt__
<__main__.Foo object at 0x7fb056f696d0>
>>> Foo < Foo()
in Foo.__gt__
<__main__.Foo object at 0x7fb0580013d0>

同样,您使用实例类型这一事实是无关紧要的。对于未实现这些运算符的任何其他对象,您将获得相同的模式:

>>> Foo() < object()
in Foo.__lt__
<__main__.Foo object at 0x7fb056f696d0>
>>> object() < Foo()
in Foo.__gt__
<__main__.Foo object at 0x7fb0580013d0>