1

What is the rationale behind the seemingly inconsistent behaviour of the following lines of code?

import numpy as np

# standard list
print(bool([]))                # False - expected
print(bool([0]))               # True  - expected
print(bool([1]))               # True  - expected
print(bool([0,0]))             # True  - expected

# numpy arrays
print(bool(np.array([])))      # False - expected, deprecation warning: The
                               # truth value of an empty array is ambiguous...
print(bool(np.array([0])))     # False - unexpected, no warning
print(bool(np.array([1])))     # True  - unexpected, no warning 
print(bool(np.array([0,0])))   # ValueError: The truth value of an array 
                               # with more than one element is ambiguous...

There are at least two inconsistencies in my point of view:

  • Standard python containers can be tested for emptiness bool(container). Why do numpy array not follow this pattern? (bool(np.array([0])) yields False)
  • Why is there an exception/deprecation warning when converting an empty numpy array or an array of length > 1, but it is okay to do so when the numpy array contains just one element?

Note that the deprecation for empty numpy arrays was added somewhere between numpy 1.11. and 1.14.

1
  • Look at np.array(...).astype(bool). Also we get that ambiguous ValueError anytime that a multi-valued boolean array is used in a context that expects a scalar boolean. Here bool, but also in if, and and/or. Commented Apr 5, 2018 at 20:49

1 Answer 1

3

For the first problem, the reason is that it's not at all clear what you want to do with if np.array([1, 2]):.

This isn't a problem for if [1, 2]: because Python lists don't do element-wise anything. The only thing you can be asking is whether the list itself is truthy (non-empty).

But Numpy arrays do everything element-wise that possibly could be element-wise. Notice that this is hardly the only place, or even the most common place, where element-wise semantics mean that arrays work differently from normal Python sequences. For example:

>>> [1, 2] * 3
[1, 2, 1, 2, 1, 2]
>>> np.array([1, 2]) * 3
array([3, 6])

And, for this case in particular, boolean arrays are a very useful thing, especially since you can index with them:

>>> arr = np.array([1, 2, 3, 4])
>>> arr > 2 # all values > 2
array([False, False,  True,  True])
>>> arr[arr > 2] = 2 # clamp the values at <= 2
>>> arr
array([1, 2, 2, 2])

And once you have that feature, it becomes ambiguous what an array should mean in a boolean context. Normally, you want the bool array. But when you write if arr:, you could mean any of multiple things:

  • Do the body of the if for each element that's truthy. (Rewrite the body as an expression on arr indexed by the bool array.)
  • Do the body of the if if any element is truthy. (Use any.)
  • Do the body of the if if all elements are truthy. (Use any.)
  • A hybrid over some axis—e.g., do the body for each row where any element is truthy.
  • Do the body of the if if the array is nonempty—acting like a normal Python sequence but violating the usual element-wise semantics of an array. (Explicitly check for emptiness.)

So, rather than guess, and be wrong more often than not, numpy gives you an error and forces you to be explicit.


For the second problem, doesn't the warning text answer this for you? The truth value of a single element is obviously not ambiguous.

And single-element arrays—especially 0D ones—are often used as pseudo-scalars, so being able to do this isn't just harmless, it's also sometimes useful.

By contrast, asking "is this array empty" is rarely useful. A list is a variable-sized thing that you usually build up by adding one element at a time, zero or more times (possibly implicitly in a comprehension), so it's very often worth asking whether you added zero elements. But an array is a fixed-size thing, where you usually explicitly specified the size somewhere nearby in the code.

That's why it's allowed. And why it operates on the single value, not on the size of the array.


For empty arrays (which you didn't ask about, but did bring up): here, instead of there being multiple reasonable things you could mean, it's hard to think of anything reasonable you could mean. Which is probably why this is the only case that's changed recently (see issue 9583), rather than being the same since the days when Python added __nonzero__.

Sign up to request clarification or add additional context in comments.

3 Comments

Since this seems to be at least as much a "why" question as a "how" question, I think my answer would be a lot better if I could find and cite design rationale documents of some kind. But I think this decision goes back into the murky depths of Numpy's long-dead predecessors, which makes that harder… If someone else does have such links, they can probably write a much better answer than mine.
The numpy issue tracker often has good discussions about these topics. Here is one on the truth-value of empty arrays. And this one on scalar conventions of size 1 arrays
@user7138814 Yeah, the numpy issue and the linked list thread has the info on the recent change to empty arrays (thanks for digging it up; I edited it into the answer). But the original discussion for arrays in general is probably somewhere on the ancient numeric list archives, and I don't even know if those are available anymore. (They used to be linked from the old sourceforge site, but I can't even Wayback my way to it anymore.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.