Table of Contents

Arrays: Truth Value Ambiguity

If you’ve been using Python for a little while, you’ve probably come across the idea of objects being truthy or falsy. In plain terms, this means that almost every object in Python can be evaluated in a boolean context, like inside an if statement, even if it’s not literally a True or False value.

For example, an empty list [] is considered falsy, while a list with at least one item is considered truthy. This behavior is convenient: you don’t need to write if len(xs) > 0, you can just write if xs.

for x in ([], ['hello world']):
    if x:
        print(f'{x = } has things in it')
    else:
        print(f'{x = } is empty!')
x = [] is empty!
x = ['hello world'] has things in it

This principle extends beyond lists. Almost all Python objects define some way of answering the question: “Are you truthy or falsy?” For containers like strings, sets, or dictionaries, emptiness determines falsy-ness. For scalars like numbers, 0 and False are falsy, while everything else is truthy.

from itertools import chain
empty_containers = [], tuple(), set(), {}, ''
falsy_scalars    = 0, False
full_containers  = ['abc'], ('a',), {'a'}, {'a': 1}, 'abc'
truthy_scalars   = 1, True

for x in chain(empty_containers, falsy_scalars, full_containers, truthy_scalars):
    if x:
        print(f'{x = !s:<10}{bool(x) = !s:<5} → is truthy')
    else:
        print(f'{x = !s:<10}{bool(x) = !s:<5} → is falsy')
x = []         → bool(x) = False → is falsy
x = ()         → bool(x) = False → is falsy
x = set()      → bool(x) = False → is falsy
x = {}         → bool(x) = False → is falsy
x =            → bool(x) = False → is falsy
x = 0          → bool(x) = False → is falsy
x = False      → bool(x) = False → is falsy
x = ['abc']    → bool(x) = True  → is truthy
x = ('a',)     → bool(x) = True  → is truthy
x = {'a'}      → bool(x) = True  → is truthy
x = {'a': 1}   → bool(x) = True  → is truthy
x = abc        → bool(x) = True  → is truthy
x = 1          → bool(x) = True  → is truthy
x = True       → bool(x) = True  → is truthy

So far, so consistent. But once you step into the world of scientific computing, things get weird. NumPy arrays (and, by extension, pandas objects) don’t follow this pattern. If you try to check the truthiness of an array, you don’t get a neat True or False. You get an error.

import numpy as np

x = np.array([1,2,3])
if x: # same as: bool(x)
    print("???")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[3], line 4
      1 import numpy as np
      3 x = np.array([1,2,3])
----> 4 if x: # same as: bool(x)
      5     print("???")

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Instead of True or False, you’ll hit the dreaded error:

The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

But we clearly have an established pattern in the core Python language. So, why did NumPy take this route?

What NumPy Simplifies

To understand the ambiguity, let’s compare a built-in Python list with a NumPy array.

Python list

xs = ['abc', 123]  # ① can store heterogeneous data
xs += [10]         # ② operations work *on* the container; this is a .extend operation
xs.append('hello') # ③ designed for elements to be appended/extended quickly

print(xs)
['abc', 123, 10, 'hello']

NumPy array

import numpy as np
xs = np.array([123, 456]) # ① only stores homogeneous data
print(f'{xs.dtype = }')   #   the data type is stored on the container metadata
xs += 10                  # ② operations work *within* the container; on each element
new_xs = np.append(xs, 0) # ③ appending is an expensive operation
print(new_xs)
xs.dtype = dtype('int64')
[133 466   0]

Within the context of evaluating the "truthiness" of an array, the crucial difference lies in ② how operations are applied.

  • lists: operations act on the container (e.g., .append, .extend).

  • arrays: operations act on the values inside the container.

Let’s make that distinction clearer.

import numpy as np

xs_list = [1,2,3]
xs_arr  = np.array(xs_list)

print(
    f'{[x + 10 for x in xs_list] =!r:>19}', # needs explicit for-loop to operate on the elements
    f'{xs_arr + 10               = }',      # defaults to operating on the elements
    sep='\n'
)
[x + 10 for x in xs_list] =       [11, 12, 13]
xs_arr + 10               = array([11, 12, 13])

With arrays, there’s no need for explicit for-loops. Arithmetic automatically applies elementwise, and NumPy’s internal optimizations make this both fast and memory-efficient.

This design philosophy of always operate on values, not the container, is what creates the truth-value ambiguity.

NumPy Operates on Values

Now consider this:

import numpy as np

xs_arr  = np.array(xs_list)
bool(xs_arr)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[7], line 4
      1 import numpy as np
      3 xs_arr  = np.array(xs_list)
----> 4 bool(xs_arr)

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

How should Python interpret this?

  • Do we mean “does this array contain anything at all?” (like a list would)?

  • Or do we mean “are the values in this array truthy?”

But what if the array has mixed values like [0, 1, 2]? Should that be True because some elements are truthy, or False because some elements are falsy? (Remember that bool(0) → False, bool(1) → True, bool(2) → True)

NumPy avoids making that decision for you. Instead, it raises an error and forces you to be explicit:

  • xs_arr.any() → at least one truthy value

  • xs_arr.all() → all values are truthy

  • len(xs_arr) > 0 → array is non-empty

Wrap-Up

Python lists and other containers make “truthiness” simple: empty means False, non-empty means True. NumPy arrays, however, live in a world where operations always apply to the elements inside. That makes the question of truthiness genuinely ambiguous, so NumPy refuses to guess.

Instead, it hands the decision back to you. Want to know if the array is empty? Check its length. Want to know if any or all values are truthy? Call .any() or .all().

It might feel strict, but this design helps prevent subtle, hard-to-catch bugs when working with large datasets or numerical computations.

What are your thoughts on truthy and falsy objects? Let me know on the DUTC Discord server!

Table of Contents
Table of Contents