Я реализую узкий и ограниченный сценарий DSL с использованием python, и я хотел бы иметь возможность функционально сделать эквивалент следующего:
import numpy as np
a = np.arange(10)
a[ a > 5 ] += 42
=> array([ 0, 1, 2, 3, 4, 5, 48, 49, 50, 51])
Приведенный выше код работает, как и следовало ожидать. Если я начну расширять приведенный выше код, я получу следующий первый слой внутренних компонентов:
a[a>5].__iadd__(42)
, который также работает как ожидалось. Однако я не могу найти метод индексатора, который позволил бы мне работать с __iadd__ над самим массивом вместо его копии. Таким образом, неудивительно, что следующий код не делает то, что я хочу:
import numpy as np
a = np.arange(10)
a.__getitem__(a>5).__iadd__(42)
=> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Только если я делаю:
a.__setitem__(a>5, a.__getitem__(a>5).__iadd__(42))
мне кажется, что я получаю поведениеищет, но на данный момент это больше не является правильным оператором присваивания на месте, и что более важно, я индексирую дважды (один раз для чтения и один раз для записи).
Индекс Нампи page , по-видимому, подразумевает, что расширенное индексирование (т. Е. Индексирование, где список нижних индексов является ndarray) всегда возвращает копию. Означает ли это, что на самом деле a[a>5].__iadd__(42)
всегда реализуется с использованием резервного метода? Я что-то упускаю, или это просто невозможно, или, по крайней мере, невозможно без магии переводчика?
Редактировать:
Таким образом, согласно ответу @donkopotamus, модель данных не позволяет нам сделать это за один снимок. Это отвечает на вопрос.
Однако, если numpy
является векторизованной библиотекой, индексирование не может позволить не быть векторизованным и выполняться несколько раз.
Вот «доказательство»:
import cython
import numpy as np
@cython.locals(arr="float[:]",
mask="bint[:]",
val=float,
i=int)
@cython.boundscheck(False)
def func(arr,mask,val):
for i in range(len(mask)):
if mask[i]:
arr[i] += val
Этот код, когда он скомпилирован и синхронизирован, на медленнее , чем numpy на месте:
a = np.arange(1e6)
%%timeit
a[a%3==0] += 42
=> 40.5 ms ± 376 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
a = np.arange(1e6)
%%timeit
func(a, (a%3==0), 42)
=> 116 ms ± 2.76 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Таким образом, оператор REPL интерпретировалработает быстрее, чем 3-строчная функция Cython, которая в значительной степени копирует представление памяти с той скоростью, с которой это позволяет процессор.
На данном этапе ни одно из них больше не имеет никакого смысла. Я знаю, что numpy создан вручную для оптимизации операций векторизации, но я не понимаю, как он интегрируется с интерпретатором Python таким образом, который имеет смысл. Это кэширует пару BINARY_SUBSCR / STORE_SUBSCR?
@ donkopotamus, обратите внимание, что хотя операция индексации не вычисляется дважды, в коде Python она интерпретируется дважды в том смысле, что маска выполняетсячтение, а затем выполняется полное второе сканирование и маска. В приведенном выше коде Cython эта операция выполняется только один раз для чтения и записи).
Любое понимание приветствуется.