context manager based alternative to Re: Proposal: === and !===

Chris Angelico rosuav at
Sat Jul 12 07:53:04 CEST 2014

On Sat, Jul 12, 2014 at 8:37 AM, Cameron Simpson <cs at> wrote:
> On 11Jul2014 14:37, Chris Angelico <rosuav at> wrote:
>> Does C-level code have to check this flag before comparing
>> nans,
> If you mean:
>   float x, y;
>   [...]
>   if (x == y) {
>     action...
>   }
> then no.
>> or is this applicable only to the Python float objects and only
>> when compared in Python?
> The former but not the latter, in my mind. Comparing Python float objects
> should obey the rule, whether in pure Python or extensions using a PyFloat.
> But I'd hope they get that for free by indirecting through PyFloat's
> methods.

Suppose you have some C extension that works with numbers (I have
NumPy in mind as I write this, but I'm not familiar with its inner
workings; certainly it'd be possible for this to be implemented how I
describe). You pack a bunch of Python float objects into an array,
pack another bunch into another array, and then compare them.

>>> from numpy import array
>>> x=array([1.0,2.0])
>>> y=array([1.0,2.1])
>>> x==y
array([ True, False], dtype=bool)

So far, so good. Now let's introduce a NaN.

>>> nan=float("nan")
>>> x=array([1.0,2.0,nan])
>>> y=array([1.0,2.1,nan])
>>> x==y
array([ True, False, False], dtype=bool)

I deliberately used the exact same nan so that an identity or
bit-pattern check would return True; but numpy says False, these are
not the same. Now, if I replace array with this list-like thing, then
your context manager would control that last result:

>>> class array(list):
     def __eq__(self, other):
         if not isinstance(other, type(self)) or len(other)!=len(self):
             return False
         return [self[i] == other[i] for i in range(len(self))]

But if, instead, the array is packed into a C-level array of floats
(which can be done without losing any information other than object
identity), then this would no longer be affected by your context
manager, unless the extension unnecessarily reconstructs two Python
floats to compare them, or else is made specifically aware of the

>> Is isnan() still usable?
> Yes.
>> (Consider that x!=x == math.isnan(x) normally.)
> Can you elaborate on this statement please?

def isnan(x):
    return isinstance(x, float) and x!=x

Fairly standard way of defining isnan(). It would be extremely
surprising if that correlation didn't work. Let's suppose you want to
sort a list of floats with all NaNs coming first:

new_list = sorted(old_list, key=lambda x: (x==x, x))
new_list = sorted(old_list, key=lambda x: (not math.isnan(x), x))

The first one is much briefer (and faster, as it doesn't need to look
up math and math.isnan and then call a function), and people will
assume they're equivalent. If this kind of thing happens deep inside a
call tree, it'll be extremely surprising that the two act differently.
So there has to be some other way to implement isnan(), which will
probably involve poking around with the bit pattern, and everyone MUST
use that instead of the simple comparison.


More information about the Python-list mailing list