Универсальные функции (ufunc) основы#

Универсальная функция (или универсальная функция (ufunc) (сокращённо) — это функция, которая работает с ndarrays поэлементно, поддерживая вещание массивов, приведение типов, и несколько других стандартных функций. То есть ufunc — это "векторизованныйобёртка для функции, которая принимает фиксированное количество конкретных входных данных и производит фиксированное количество конкретных выходных данных.

В NumPy универсальные функции являются экземплярами numpy.ufunc класса. Многие встроенные функции реализованы в скомпилированном C-коде. Базовые ufunc работают со скалярами, но также существует обобщённый вид, где базовые элементы — подмассивы (векторы, матрицы и т.д.), и трансляция выполняется по другим измерениям. Простейший пример — оператор сложения:

>>> np.array([0,2,3,4]) + np.array([1,1,-1,2])
array([1, 3, 2, 6])

Можно также создавать пользовательские numpy.ufunc экземпляры с использованием numpy.frompyfunc фабричная функция.

Методы ufunc#

Все ufuncs имеют четыре метода. Их можно найти на Методы. Однако эти методы имеют смысл только для скалярных ufuncs, которые принимают два входных аргумента и возвращают один выходной аргумент. Попытка вызвать эти методы для других ufuncs вызовет ValueError.

Все методы, подобные reduce, принимают ось ключевое слово, dtype ключевое слово, и выход ключевое слово, и все массивы должны иметь размерность >= 1. ось ключевое слово указывает ось массива, по которой будет выполнено сокращение (с отрицательными значениями, отсчитываемыми назад). Обычно это целое число, хотя для numpy.ufunc.reduce, это также может быть кортеж из int для сокращения по нескольким осям одновременно, или None, чтобы выполнить редукцию по всем осям. Например:

>>> x = np.arange(9).reshape(3,3)
>>> x
array([[0, 1, 2],
      [3, 4, 5],
      [6, 7, 8]])
>>> np.add.reduce(x, 1)
array([ 3, 12, 21])
>>> np.add.reduce(x, (0, 1))
36

The dtype ключевое слово позволяет управлять очень распространенной проблемой, которая возникает при наивном использовании ufunc.reduce. Иногда у вас может быть массив определенного типа данных, и вы хотите сложить все его элементы, но результат не помещается в тип данных массива. Это часто происходит, если у вас есть массив однобайтовых целых чисел. Параметр dtype ключевое слово позволяет изменить тип данных, над которым выполняется редукция (и, следовательно, тип выходных данных). Таким образом, вы можете гарантировать, что выходные данные имеют тип данных с достаточной точностью для обработки вашего результата. Ответственность за изменение типа редукции в основном лежит на вас. Есть одно исключение: если нет dtype указан для операции редукции "сложение" или "умножение", то если тип входных данных целочисленный (или логический) и меньше размера numpy.int_ тип данных будет внутренне приведен к int_ (или numpy.uint) тип данных. В предыдущем примере:

>>> x.dtype
dtype('int64')
>>> np.multiply.reduce(x, dtype=float)
array([ 0., 28., 80.])

Наконец, выход ключевое слово позволяет вам предоставить выходной массив (или кортеж выходных массивов для много-выходных ufuncs). Если выход указан, то dtype аргумент используется только для внутренних вычислений. Учитывая x из предыдущего примера:

>>> y = np.zeros(3, dtype=int)
>>> y
array([0, 0, 0])
>>> np.multiply.reduce(x, dtype=float, out=y)
array([ 0, 28, 80])

Ufuncs также имеют пятый метод, numpy.ufunc.at, который позволяет выполнять операции на месте с использованием расширенной индексации. Нет буферизация используется для измерений, где применяется расширенная индексация, поэтому расширенный индекс может перечислять элемент более одного раза, и операция будет выполнена над результатом предыдущей операции для этого элемента.

Определение типа вывода#

Если входные аргументы уфункции (или её методов) являются ndarrays, тогда вывод также будет таким. Исключение составляет случай, когда результат является нульмерным, и в этом случае вывод будет преобразован в скаляр массива. Этого можно избежать, передав out=... или out=Ellipsis.

Если некоторые или все входные аргументы не являются ndarrays, тогда вывод может не быть ndarray тоже. Действительно, если любой вход определяет __array_ufunc__ метод, управление будет полностью передано этой функции, т.е. универсальная функция переопределено.

Если ни один из входных данных не переопределяет ufunc, то все выходные массивы будут переданы в __array_wrap__ метод ввода (кроме ndarrays, и скаляры), которые определяют его и имеет наивысший __array_priority__ любого другого ввода универсальной функции. По умолчанию __array_priority__ ndarray равен 0.0, а значение по умолчанию __array_priority__ подтипа равно 0.0. Матрицы имеют __array_priority__ равно 10.0.

Все универсальные функции также могут принимать выходные аргументы, которые должны быть массивами или подклассами. При необходимости результат будет приведен к типам данных предоставленных выходных массивов. Если выходной массив имеет __array_wrap__ метод, который вызывается вместо найденного во входных данных.

Трансляция (Broadcasting)#

Смотрите также

Основы трансляции

Каждая универсальная функция принимает массивы на входе и выдаёт массивы на выходе, выполняя основную функцию поэлементно на входных данных (где элемент обычно является скаляром, но может быть вектором или подмассивом более высокого порядка для обобщённых универсальных функций). Стандартные правила трансляции применяются так, что входные данные, не имеющие точно одинаковых форм, всё ещё могут быть полезно обработаны.

Согласно этим правилам, если вход имеет размерность 1 в своей форме, первая запись данных в этой размерности будет использоваться для всех вычислений вдоль этой размерности. Другими словами, механизм шагания универсальная функция (ufunc) просто не будет перемещаться вдоль этого измерения ( шаг будет равно 0 для этого измерения).

Правила приведения типов#

Примечание

В NumPy 1.6.0 был создан API продвижения типов для инкапсуляции механизма определения выходных типов. См. функции numpy.result_type, numpy.promote_types, и numpy.min_scalar_type для получения дополнительной информации.

В основе каждой универсальной функции лежит одномерный цикл с шагом, который реализует фактическую функцию для конкретной комбинации типов. При создании универсальной функции ей предоставляется статический список внутренних циклов и соответствующий список сигнатур типов, над которыми работает универсальная функция. Механизм универсальных функций использует этот список, чтобы определить, какой внутренний цикл использовать для конкретного случая. Вы можете проверить .types атрибут для конкретного ufunc, чтобы увидеть, какие комбинации типов имеют определённый внутренний цикл и какой выходной тип они производят (символьные коды используются в указанном выводе для краткости).

Приведение типов должно выполняться для одного или нескольких входных данных, когда ufunc не имеет реализации основного цикла для предоставленных типов входных данных. Если реализация для входных типов не найдена, алгоритм ищет реализацию с сигнатурой типов, к которой все входные данные могут быть приведены «безопасно». Первая найденная во внутреннем списке циклов выбирается и выполняется после всего необходимого приведения типов. Напомним, что внутренние копии во время ufunc (даже для приведения) ограничены размером внутреннего буфера (который настраивается пользователем).

Примечание

Универсальные функции в NumPy достаточно гибки, чтобы иметь смешанные типы сигнатур. Таким образом, например, можно определить универсальную функцию, которая работает со значениями с плавающей точкой и целыми числами. См. numpy.ldexp для примера.

Согласно приведенному выше описанию, правила приведения типов по сути реализуются вопросом о том, когда тип данных может быть безопасно приведен к другому типу данных. Ответ на этот вопрос может быть определен в Python с помощью вызова функции: can_cast(fromtype, totype). Пример ниже показывает результаты этого вызова для 24 внутренне поддерживаемых типов на 64-битной системе автора. Вы можете сгенерировать эту таблицу для своей системы с помощью кода, приведённого в примере.

Пример

Сегмент кода, показывающий таблицу "безопасного приведения типов" для 64-битной системы. Обычно вывод зависит от системы; ваша система может показать другую таблицу.

>>> mark = {False: ' -', True: ' Y'}
>>> def print_table(ntypes):
...     print('X ' + ' '.join(ntypes))
...     for row in ntypes:
...         print(row, end='')
...         for col in ntypes:
...             print(mark[np.can_cast(row, col)], end='')
...         print()
...
>>> print_table(np.typecodes['All'])
X ? b h i l q n p B H I L Q N P e f d g F D G S U V O M m
? Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y
b - Y Y Y Y Y Y Y - - - - - - - Y Y Y Y Y Y Y Y Y Y Y - Y
h - - Y Y Y Y Y Y - - - - - - - - Y Y Y Y Y Y Y Y Y Y - Y
i - - - Y Y Y Y Y - - - - - - - - - Y Y - Y Y Y Y Y Y - Y
l - - - - Y Y Y Y - - - - - - - - - Y Y - Y Y Y Y Y Y - Y
q - - - - Y Y Y Y - - - - - - - - - Y Y - Y Y Y Y Y Y - Y
n - - - - Y Y Y Y - - - - - - - - - Y Y - Y Y Y Y Y Y - Y
p - - - - Y Y Y Y - - - - - - - - - Y Y - Y Y Y Y Y Y - Y
B - - Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y - Y
H - - - Y Y Y Y Y - Y Y Y Y Y Y - Y Y Y Y Y Y Y Y Y Y - Y
I - - - - Y Y Y Y - - Y Y Y Y Y - - Y Y - Y Y Y Y Y Y - Y
L - - - - - - - - - - - Y Y Y Y - - Y Y - Y Y Y Y Y Y - -
Q - - - - - - - - - - - Y Y Y Y - - Y Y - Y Y Y Y Y Y - -
N - - - - - - - - - - - Y Y Y Y - - Y Y - Y Y Y Y Y Y - -
P - - - - - - - - - - - Y Y Y Y - - Y Y - Y Y Y Y Y Y - -
e - - - - - - - - - - - - - - - Y Y Y Y Y Y Y Y Y Y Y - -
f - - - - - - - - - - - - - - - - Y Y Y Y Y Y Y Y Y Y - -
d - - - - - - - - - - - - - - - - - Y Y - Y Y Y Y Y Y - -
g - - - - - - - - - - - - - - - - - - Y - - Y Y Y Y Y - -
F - - - - - - - - - - - - - - - - - - - Y Y Y Y Y Y Y - -
D - - - - - - - - - - - - - - - - - - - - Y Y Y Y Y Y - -
G - - - - - - - - - - - - - - - - - - - - - Y Y Y Y Y - -
S - - - - - - - - - - - - - - - - - - - - - - Y Y Y Y - -
U - - - - - - - - - - - - - - - - - - - - - - - Y Y Y - -
V - - - - - - - - - - - - - - - - - - - - - - - - Y Y - -
O - - - - - - - - - - - - - - - - - - - - - - - - - Y - -
M - - - - - - - - - - - - - - - - - - - - - - - - Y Y Y -
m - - - - - - - - - - - - - - - - - - - - - - - - Y Y - Y

Следует отметить, что хотя типы 'S', 'U' и 'V' включены в таблицу для полноты, они не могут обрабатываться ufunc. Также обратите внимание, что в 32-битной системе целочисленные типы могут иметь разные размеры, что приводит к слегка измененной таблице.

Смешанные операции скаляр-массив используют другой набор правил приведения, который гарантирует, что скаляр не может "повысить тип" массива, если скаляр имеет принципиально другой вид данных (т.е., находится под другим уровнем в иерархии типов данных), чем массив. Это правило позволяет вам использовать скалярные константы в вашем коде (которые, как типы Python, интерпретируются соответствующим образом в ufuncs), не беспокоясь о том, вызовет ли точность скалярной константы повышение типа вашего большого массива (с малой точностью).

Использование внутренних буферов#

Внутренне буферы используются для невыровненных данных, переставленных данных и данных, которые должны быть преобразованы из одного типа данных в другой. Размер внутренних буферов устанавливается на уровне потока. Может быть до \(2 (n_{\mathrm{inputs}} + n_{\mathrm{outputs}})\) буферы указанного размера созданы для обработки данных всех входов и выходов ufunc. Размер буфера по умолчанию составляет 10 000 элементов. Всякий раз, когда требуется вычисление на основе буфера, но все входные массивы меньше размера буфера, эти некорректно работающие или неправильно типизированные массивы будут скопированы перед продолжением вычисления. Настройка размера буфера может, следовательно, изменить скорость, с которой выполняются вычисления ufunc различных типов. Простой интерфейс для установки этой переменной доступен с помощью функции numpy.setbufsize.

Обработка ошибок#

Универсальные функции могут задействовать специальные регистры состояния чисел с плавающей точкой в вашем оборудовании (например, деление на ноль). Если они доступны на вашей платформе, эти регистры будут регулярно проверяться во время вычислений. Обработка ошибок контролируется для каждого потока и может быть настроена с помощью функций numpy.seterr и numpy.seterrcall.

Переопределение поведения ufunc#

Классы (включая подклассы ndarray) могут переопределять поведение ufuncs на них путем определения определенных специальных методов. Для подробностей см. Стандартные подклассы массивов.