# Rich Comparisons Gotcha

James Stroud jstroud at mbi.ucla.edu
Mon Dec 8 04:36:17 CET 2008

```Robert Kern wrote:
> James Stroud wrote:
>> I'm missing how a.all() solves the problem Rasmus describes, namely
>> that the order of a python *list* affects the results of containment
>> tests by numpy.array. E.g. "y in ll1" and "y in ll2" evaluate to
>> different results in his example. It still seems like a bug in numpy
>> to me, even if too much other stuff is broken if you fix it (in which
>> case it apparently becomes an "issue").
>
> It's an issue, if anything, not a bug. There is no consistent
> implementation of bool(some_array) that works in all cases. numpy's
> predecessor Numeric used to implement this as returning True if at least
> one element was non-zero. This works well for bool(x!=y) (which is
> equivalent to (x!=y).any()) but does not work well for bool(x==y) (which
> should be (x==y).all()), but many people got confused and thought that
> bool(x==y) worked. When we made numpy, we decided to explicitly not
> allow bool(some_array) so that people will not write buggy code like
> this again.
>
> The deficiency is in the feature of rich comparisons, not numpy's
> implementation of it. __eq__() is allowed to return non-booleans;
> however, there are some parts of Python's implementation like
> list.__contains__() that still expect the return value of __eq__() to be
> meaningfully cast to a boolean.
>

You have explained

py> 112 = [1, y]
py> y in 112
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is...

but not

py> ll1 = [y,1]
py> y in ll1
True

It's this discrepancy that seems like a bug, not that a ValueError is
raised in the former case, which is perfectly reasonable to me.

All I can imagine is that something like the following lives in the
bowels of the python code for list:

def __contains__(self, other):
foundit = False
for i, v in enumerate(self):
if i == 0:
# evaluates to bool numpy array
foundit = one_kind_of_test(v, other)
else:
# raises exception for numpy array
foundit = another_kind_of_test(v, other)
if foundit:
break
return foundit

I'm trying to imagine some other way to get the results mentioned but I
honestly can't. It's beyond me why someone would do such a thing, but
perhaps it's an optimization of some sort.

James

```