6.10. Широковещание

Когда бинарный оператор получает два массива, формы которых не совпадают в точности, numpy не вызывает ошибку – он выполняет широковещание. Широковещание – это небольшой набор правил, которые определяют, совместимы ли две формы, и если да, то как меньшая виртуально растягивается до совпадения с большей.

6.10.1. Правила

Когда два операнда имеют формы A и B, numpy обрабатывает их в два шага.

  1. Выравнивание рангов. Если у одного операнда меньше осей, чем у другого, numpy виртуально дополняет начало его формы осями размера 1, пока обе формы не получат одинаковое число осей. Одномерный операнд формы (3,) в паре с двумерным операндом формы (2, 3) превращается в (1, 3) против (2, 3).

  2. Проверка каждой оси. Проходя по теперь равным по длине формам ось за осью, каждая пара размеров должна удовлетворять одному из двух условий: размеры равны или один из них равен 1. Ось размера 1 виртуально растягивается до размера противоположной стороны для выполнения операции. Пара (1, 3) против (2, 3) совместима, потому что первая ось содержит 1 (растягивается до 2), а вторая ось совпадает (3 == 3); результат имеет форму (2, 3).

Если какая-либо пара осей не удовлетворяет ни одному из условий, формы несовместимы и оператор вызывает ValueError.

6.10.2. Примеры

Скаляр против любого массива. Скаляр действует как форма (1,) и растягивается до чего угодно:

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10                 # (2, 3) + scalar -> (2, 3)

Одномерный вектор по двумерной матрице. Правило 1 добавляет в начало ось размера 1, превращая (3,) в (1, 3); правило 2 затем растягивает эту строку вниз по каждому столбцу a:

row = np.array([100, 200, 300], dtype=np.float)
a + row                # (2, 3) + (3,) -> (2, 3)

Два одномерных массива равной длины складываются поэлементно – широковещание не требуется:

np.arange(4) + np.arange(4)

Вектор-столбец против вектора-строки образует двумерную «внешнюю» форму: (4, 1) в паре с (3,) после добавления ранга становится (4, 1) против (1, 3), и правило 2 растягивает каждый операнд вдоль его оси размера 1:

x = np.array([1, 2, 3, 4]).reshape((4, 1))     # column
y = np.array([10, 20, 30])                      # row
x + y                                           # (4, 3) matrix

Те же правила формы применяются к любой двухаргументной ufunc, включая arctan2():

np.arctan2(y, 1.0)
np.arctan2(y, x)

6.10.3. Что широковещание не выделяет

Растяжение виртуально. numpy проходит оба операнда совместно, перечитывая меньший вдоль его оси широковещания, а не копируя его. Данные меньшего массива никогда не дублируются в памяти.

Для памяти важен размер выходного массива. a + row выделяет выход формы a, а не формы a плюс формы row. Длинные цепочки широковещания всё же могут порождать большие промежуточные результаты.

6.10.4. Когда широковещание идёт не так

Классический сбой – две формы, ни у одной из которых нет оси размера 1 для растяжения, а размеры не совпадают – например, (3, 4) против (4, 3). Правило 2 не может сопоставить 3 с 4, поэтому numpy вызывает ValueError.

Более тонкая проблема – широковещание, которое проходит успешно, но не так, как задумало приложение. (5,) против (5, 1) – канонический случай: добавление ранга превращает (5,) в (1, 5), что широковещается против (5, 1), образуя матрицу (5, 5) – внешнюю комбинацию двух векторов, а не поэлементный результат длины 5, который, вероятно, нужен был приложению. В случае сомнений выведите shape для обеих сторон и пройдите по правилам, прежде чем прибегать к reshape() или transpose().