osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Why does __ne__ exist?


On Sunday, January 7, 2018 at 7:55:57 PM UTC, Chris Angelico wrote:
> Whoops, premature send. Picking up from the last paragraph.
> 
> This is good. This is correct. For inequalities, you can't assume that
> >= is the exact opposite of < or the combination of < and == (for
> example, sets don't behave like numbers, so "x <= y" is very different
> from "x < y or x == y"). But the one that confuses me is != or __ne__.
> If you don't create it, you get default behaviour:
> 
> >>> class Ham:
> ...     def __eq__(self, other):
> ...         print("%s equals %s" % (self, other))
> ...         return True
> ...
> >>> Ham() == 1
> <__main__.Ham object at 0x7fb7557c0278> equals 1
> True
> >>> 2 == Ham()
> <__main__.Ham object at 0x7fb7557c0278> equals 2
> True
> >>> Ham() != 3
> <__main__.Ham object at 0x7fb7557c0278> equals 3
> False
> >>> 4 != Ham()
> <__main__.Ham object at 0x7fb7557c0278> equals 4
> False
> >>> x = Ham()
> >>> x == x
> <__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
> 0x7fb7557b80f0>
> True
> >>> x != x
> <__main__.Ham object at 0x7fb7557b80f0> equals <__main__.Ham object at
> 0x7fb7557b80f0>
> False
> 
> Under what circumstances would you want "x != y" to be different from
> "not (x == y)" ? How would this make for sane behaviour? Even when
> other things go weird with equality checks, that basic parallel is
> always maintained:
> 
> >>> z = float("nan")
> >>> z == z
> False
> >>> z != z
> True
> 
> Python gives us a "not in" operator that uses __contains__ and then
> negates the result. There is no way for "x not in y" to be anything
> different from "not (x in y)", as evidenced by the peephole optimizer:
> 
> >>> dis.dis("x not in y")
>   1           0 LOAD_NAME                0 (x)
>               2 LOAD_NAME                1 (y)
>               4 COMPARE_OP               7 (not in)
>               6 RETURN_VALUE
> >>> dis.dis("not (x in y)")
>   1           0 LOAD_NAME                0 (x)
>               2 LOAD_NAME                1 (y)
>               4 COMPARE_OP               7 (not in)
>               6 RETURN_VALUE
> 
> So why isn't != done the same way? Is it historical?
> 
> ChrisA

I'd say this is certainly historical, remembering that in Python 2 you used to be able to compare all sorts of things, whereas in Python 3 you'll get:-

>>> 1 < "a"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'str'
>>> 

This seems to be confirmed by the following.  From the third paragraph at https://docs.python.org/2/reference/datamodel.html#object.__ne__ "There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining __eq__(), one should also define __ne__() so that the operators will behave as expected...".  Compare that with the Python 3 equivalent "By default, __ne__() delegates to __eq__() and inverts the result unless it is NotImplemented. There are no other implied relationships among the comparison operators, for example, the truth of (x<y or x==y) does not imply x<=y..."

--
Kindest regards.

Mark Lawrence.