← Python
ex00June 21, 2026 · 9 min read

Lists, NumPy Arrays & Vectorized Operations

Why Python lists are not arrays, how NumPy changes the game, and how to compute a BMI vector with clean type and shape error handling.

pythonnumpyarraysdatascience

The very first exercise of the Array piscine looks innocent — "compute a BMI" — but it is really an introduction to the single most important idea in scientific Python: the difference between a list and an array, and the power of vectorized operations.

The exercise

You must write two functions:

def give_bmi(height: list[int | float],
             weight: list[int | float]) -> list[int | float]:
    # returns the BMI for each (height, weight) pair

def apply_limit(bmi: list[int | float], limit: int) -> list[bool]:
    # returns True where bmi is strictly above the limit

And you have to handle the error cases: the two lists must be the same length, and every element must be an int or a float.

A Python list is not an array

People coming from C, Java or NumPy often assume a Python list is a contiguous block of numbers. It is not. A list is a dynamic array of pointers to arbitrary objects. That flexibility (it can hold an int, a string and a function at once) comes at a cost:

  • No math operators. [1, 2] + [3, 4] does not add element-wise — it concatenates into [1, 2, 3, 4]. And [1, 2] * 3 repeats, it does not scale.
  • Slow numerics. Every element is a boxed Python object, so looping over millions of them is heavy.
  • No shape. A list has a length, not a multidimensional shape.

Enter NumPy

NumPy's ndarray is the real array: a single, typed, contiguous buffer with a known dtype and shape. The same expression now means the math you expect:

import numpy as np

a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

a + b      # array([5, 7, 9])   element-wise
a * 2      # array([2, 4, 6])   scalar broadcast
a / b      # array([0.25, 0.4, 0.5])

This is vectorization: you express an operation over the whole array at once, and NumPy runs the loop in optimized C. You write less code and it runs orders of magnitude faster.

BMI, the vectorized way

The Body Mass Index formula is weight / height². With NumPy we never write an explicit loop:

import numpy as np

def give_bmi(height, weight):
    """Return the BMI for each height/weight pair."""
    height = np.array(height, dtype=float)
    weight = np.array(weight, dtype=float)
    if height.shape != weight.shape:
        raise ValueError("height and weight must have the same length")
    return list(weight / (height ** 2))

The whole computation is the single expression weight / (height ** 2). Both ** and / are applied to every element, pair by pair.

Boolean masks: apply_limit

Comparing an array to a scalar produces a boolean array — one of NumPy's most useful features, the foundation of filtering and masking:

def apply_limit(bmi, limit):
    """Return True where bmi is strictly above limit."""
    return list(np.array(bmi) > limit)

For [22.5, 29.0] with a limit of 26 you get [False, True] — exactly the expected output.

Error handling: the part that is actually graded

The subject is explicit: handle lists of different sizes and non-numeric content. Validation is what separates a script from a function:

def give_bmi(height, weight):
    if len(height) != len(weight):
        raise ValueError("lists must be the same size")
    for value in height + weight:
        if not isinstance(value, (int, float)):
            raise TypeError("values must be int or float")
    ...

Two subtle points:

  • isinstance(value, (int, float)) accepts both required types in one check. Beware that in Python bool is a subclass of int, so True would sneak through — reject it explicitly if you care.
  • Raise a specific exception (ValueError, TypeError) with a clear message. The piscine rule "any uncaught exception invalidates the exercise" means your main() wraps calls in try/except and prints the message.

Takeaways

  • A Python list is a container of objects; a NumPy ndarray is a typed numeric buffer.
  • Vectorized operations replace explicit loops — shorter, faster, clearer.
  • Comparisons yield boolean arrays, the gateway to masking and filtering.
  • Validate shape and dtype before you compute, and fail with a precise exception.