I am very confused. Say I have a class (which I do) where every operator and comparator (+
, -
, <
, >=
, ==
, etc.) just returns itself. In case you don't understand, here's the code:
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
I've noticed an inconsistency. The following work:
_()+_
_()-_
_()*_
_()/_
_()//_
_()>_
_()<_
_()==_
_()>=_
_()<=_
_<_()
_>_()
_==_()
_<=_()
_>=_()
But the following don't:
_+_()
_-_()
_*_()
_/_()
_//_()
They give the following error:
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'type' and '_'
In summary, comparators work with type vs instance both ways, but operators only work when the instance is on the left of the operator. Why is this?
The fact that you are using the type in these comparisons is irrelevant and confusing. You'll see the same behavior with any arbitrary object that doesn't implement the operators. So just create another class, class Foo: pass
, and you'll see the same behavior if you used a Foo()
instance. Or just an object()
instance.
Anyway, the arithmetic dunder methods all have a swapped argument version, e.g. for __add__
it's __radd__
(I think of it as "right add"). If you have x + y
, and x.__add__
is not implemented, it tries to use y.__radd__
.
Now, for the comparison operators, there are no __req__
and __rgt__
operators. Instead, the other operators themselves do this. From the 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.
So, in the cases where you have the type on the left, e.g.
_<_()
Then type.__lt__
doesn't exist, so it tries _.__gt__
, which does exist.
To demonstrate:
>>> 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>
Also, again, the fact that you are using the type of the instance is irrelevant. You'd get the same pattern with any other object that doesn't implement these operators:
>>> Foo() < object()
in Foo.__lt__
<__main__.Foo object at 0x7fb056f696d0>
>>> object() < Foo()
in Foo.__gt__
<__main__.Foo object at 0x7fb0580013d0>