On Mon, Jan 8, 2018 at 7:13 AM, Thomas Jollans <t...@tjol.eu> wrote: > On 07/01/18 20:55, Chris Angelico wrote: >> Under what circumstances would you want "x != y" to be different from >> "not (x == y)" ? > > In numpy, __eq__ and __ne__ do not, in general, return bools. > >>>> a = np.array([1,2,3,4]) >>>> b = np.array([0,2,0,4]) >>>> a == b > array([False, True, False, True], dtype=bool) >>>> a != b > array([ True, False, True, False], dtype=bool)
Thanks, that's the kind of example I was looking for. Though numpy doesn't drive the core language development much, so the obvious next question is: was this why __ne__ was implemented, or was there some other reason? This example shows how it can be useful, but not why it exists. >>>> not (a == b) > Traceback (most recent call last): > File "<stdin>", line 1, in <module> > ValueError: The truth value of an array with more than one element is > ambiguous. Use a.any() or a.all() Which means that this construct is still never going to come up in good code. >>>> ~(a == b) > array([ True, False, True, False], dtype=bool) >>>> > > I couldn't tell you why this was originally allowed, but it does turn > out to be strangely useful. (As far as the numpy API is concerned, it > would be even nicer if 'not' could be overridden, IMHO) I'm glad 'not' can't be overridden; it'd be too hard to reason about a piece of code if even the basic boolean operations could change. If you want overridables, you have &|~ for the bitwise operators (which is how numpy does things). Has numpy ever asked for a "not in" dunder method (__not_contained__ or something)? It's a strange anomaly that "not (x in y)" can be perfectly safely optimized to "x not in y", yet basic equality has to have separate handling. The default handling does mean that you can mostly ignore __ne__ and expect things to work, but if you subclass something that has both, it'll break: class Foo: def __eq__(self, other): print("Foo: %s == %s" % (self, other)) return True def __ne__(self, other): print("Foo: %s != %s" % (self, other)) return False class Bar(Foo): def __eq__(self, other): print("Bar: %s == %s" % (self, other)) return False >>> Bar() == 1 Bar: <__main__.Bar object at 0x7f40ebf3a128> == 1 False >>> Bar() != 1 Foo: <__main__.Bar object at 0x7f40ebf3a128> != 1 False Obviously this trivial example looks stupid, but imagine if the equality check in the subclass USUALLY gives the same result as the superclass, but differs in rare situations. Maybe you create a "bag" class that functions a lot like collections.Counter but ignores zeroes when comparing: >>> class Bag(collections.Counter): ... def __eq__(self, other): ... # Disregard any zero entries when comparing to another Bag ... return {k:c for k,c in self.items() if c} == {k:c for k,c in other.items() if c} ... >>> b1 = Bag("aaabccdd") >>> b2 = Bag("aaabccddq") >>> b2["q"] -= 1 >>> b1 == b2 True >>> b1 != b2 True >>> dict(b1) == dict(b2) False >>> dict(b1) != dict(b2) True The behaviour of __eq__ is normal and sane. But since there's no __ne__, the converse comparison falls back on dict.__ne__, not on object.__ne__. ChrisA -- https://mail.python.org/mailman/listinfo/python-list