Proposal: === and !=== operators

Steven D'Aprano steve at pearwood.info
Wed Jul 9 11:02:55 CEST 2014


On Wed, 09 Jul 2014 18:17:23 +1000, Cameron Simpson wrote:

> On 09Jul2014 07:00, Steven D'Aprano <steve at pearwood.info> wrote:
>>At the moment, Python has two (in)equality operators, == and != which
>>call __eq__ and __ne__ methods. Some problems with those:
>>
>>* Many people expect == to always be reflexive (that is, x == x for
>>  every x) but classes which customise __eq__ may not be.
> 
> I'm presuming this proposal is fallout from the Nan anecdotes, since NaN
> != Nan?

There is *yet another* bloody argument going on about NANs on Python-Dev, 
from people who (for the most part) don't do numeric programming but feel 
sure that they know better than William Kahan and the IEEE-754 committee 
that designed it :-(


> The language spec is at least up front about it, I thought. It could be
> plainer, but at least it says:
> 
>    Furthermore, some types (for example, function objects) support only
>    a degenerate notion of comparison where any two objects of that type
>    are unequal.
> 
> which implies nonreflexivity.

Functions inherit the default behaviour of __eq__ from object, which 
falls back on identity:


py> def f(): return 1
... 
py> f == f
True



> Returning to Nan, I had thought it was an explicit design choice in IEEE
> floating point that NaN != NaN so that in (hypothetically) common cases
> results won't accidentally issue truthiness in the vein of propagating
> evaluation errors.

There are various reasons for why the NANs always compare unordered 
(including unequal), but yes it is a deliberate decision. Unfortunately 
people who aren't doing numeric work (and a few who are) don't like it, 
and don't like that it breaks certain "common sense" 



> 
> Personally I'd go for Nan == Nan raising a ValueError myself, but that
> is a bikeshed I lack the expertise to paint.

If we can do other nonsensical comparisons and get False, why treat NANs 
differently?

py> "Hello World" == {2.5: None}
False


For what it's worth, the IEEE-754 standard supports the "exception on 
comparison" model with signalling NANs, unfortunately C99 does not 
support signalling NANs and Java explicitly forbids them.

 
> Anyway, I thought it is a design feature that a class can arrange for
> nonreflexivity in ==. Surprising, maybe, but wouldn't use of such a
> special class be known to the user?

Well yes, but then you have folks like Anders ("NaN comparisons - Call 
For Anecdotes" thread) who didn't know that floats are not reflexive (as 
well as not transitive, associative, or commutative). He's hardly the 
only one -- Stackoverflow appears to get a question asking about NANs 
about three times a week. More broadly, if you're writing a generic 
library which will be used with arbitrary objects, there are very few 
assumptions you can make about them -- nevertheless people do.


> Have we got some examples of people using nonreflexive == classes and
> being burnt? Aside from Nan, which I'd argue is a well known special
> case, or should be.

Yes it should be, and no I don't, but people are *extremely* vehement 
that x == x ought to return True for any x. In my opinion, reflexivity is 
not that important outside of pure mathematics and logic, and people only 
get upset about the lack of it because it goes against their intuition 
about what it means for two things to be equal. But other clever people 
disagree, and even though they're wrong *wink* I'd rather seek a 
compromise that gives everybody what they want.


>>* The == operator requires __eq__ to return True or False
>>  (or NotImplemented) and raises TypeError if it doesn't, which makes it
>>  impossible to use == with (say) three-valued or fuzzy logic.

Hmmm... I could have sworn that == raised an exception if __eq__ returned 
something other than True/False/NotImplemented, but apparently I was 
wrong. Maybe I dreamt it.


> I don't see this type constraint you describe.

Neither do I.


>>I propose:
>>
>>* The == operator be redefined to *always* assume reflexivity, that
>>  is, it first compares the two arguments using `is` before calling the
>>  __eq__ methods.
> 
> Won't this slow down every == test?

Only by a pointer comparison, which is very fast. Compared to the cost of 
looking up __eq__ and calling it, the extra cost will be insignificant, 
and since many objects (small ints, certain strings, etc.) are cached, 
the over-all result will probably be to speed up the average == test.


>>* That's a backwards-incompatible change, so you need to enable it
>>  using "from __future__ import equals" in Python 3.5, and then to
>>  become the default behaviour in 3.6.
>>
>>* To support non-reflexive types, allow === and !=== operators, which
>>  are like == and != except they don't call `is` first.
> [...]
> 
> I don't like the spelling. They seem very easy to misuse as typos of
> conventional == and !=, and not visually very different.

If you can tell the difference between x=y and x==y you should be able to 
also distinguish x===y. But I accept that it's a little sub-optimal.


-- 
Steven



More information about the Python-list mailing list