为什么 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>
我很困惑。假设我有一个 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>