Являются ли массивы numpy переданными по ссылке?
Я столкнулся с тем, что массивы numpy
передаются по ссылке в нескольких местах, но затем, когда я выполняю следующий код, почему существует разница между поведением foo
и bar
import numpy as np
def foo(arr):
arr = arr - 3
def bar(arr):
arr -= 3
a = np.array([3, 4, 5])
foo(a)
print a # prints [3, 4, 5]
bar(a)
print a # prints [0, 1, 2]
Я использую python 2.7 и numpy version 1.6.1
Ответы
Ответ 1
В Python все имена переменных являются ссылками на значения.
Когда Python оценивает назначение, правая сторона оценивается перед левой стороной. arr - 3
создает новый массив; он не изменяет arr
на месте.
arr = arr - 3
делает локальную переменную arr
ссылкой на этот новый массив. Он не изменяет значение, первоначально ссылающееся на arr
, которое было передано в foo
. Имя переменной arr
просто привязывается к новому массиву, arr - 3
. Кроме того, arr
- это локальное имя переменной в области foo
. Как только функция foo
завершается, больше нет ссылки на arr
, и Python может мусор собирать значение, которое он ссылается. Как указывает Reti43, для того, чтобы значение arr
повлияло на a
, foo
должно возвращать arr
, а a
должно быть присвоено этому значению
def foo(arr):
arr = arr - 3
return arr
# or simply combine both lines into `return arr - 3`
a = foo(a)
В отличие от arr -= 3
, который Python переводит в вызов __iadd__
специального метода, модифицирует массив, на который ссылается arr
на месте.
Ответ 2
Первая функция вычисляет (arr - 3)
, затем присваивает ей локальное имя arr
, которое не влияет на данные массива, переданные в. Моя догадка заключается в том, что во второй функции np.array
переопределяет -=
оператора и работает на данных массива.
Ответ 3
Python передает массив по ссылке:
$:python
...python startup message
>>> import numpy as np
>>> x = np.zeros((2,2))
>>> x
array([[0.,0.],[0.,0.]])
>>> def setx(x):
... x[0,0] = 1
...
>>> setx(x)
>>> x
array([[1.,0.],[0.,0.]])
Верхний ответ относится к явлению, которое происходит даже в скомпилированном c-коде, поскольку любые события BLAS будут включать в себя этап "чтения", при котором формируется либо новый массив, о котором знает пользователь (в данном случае, пишущий код) или новый массив формируется "под капотом" во временной переменной, о которой пользователь не знает (вы можете увидеть это как .eval()
).
Тем не менее, я могу четко получить доступ к памяти массива, как если бы он находился в более глобальной области видимости, чем вызываемая функция (т.е. setx(...)
); это именно то, что "передача по ссылке", с точки зрения написания кода.
И давайте проведем еще несколько тестов, чтобы проверить правильность принятого ответа:
(continuing the session above)
>>> def minus2(x):
... x[:,:] -= 2
...
>>> minus2(x)
>>> x
array([[-1.,-2.],[-2.,-2.]])
Кажется, будет передано по ссылке. Давайте сделаем вычисление, которое определенно вычислит промежуточный массив под капотом, и посмотрим, будет ли x изменен так, как если бы он был передан по ссылке:
>>> def pow2(x):
... x = x * x
...
>>> pow2(x)
>>> x
array([[-1.,-2.],[-2.,-2.]])
Да, я думал, что x был передан по ссылке, но, возможно, это не так? - Нет, здесь, мы затеняли x новым объявлением (которое скрыто через интерпретацию в python), и python не будет распространять это "затенение" обратно в глобальную область видимости (что нарушит случай использования python: а именно быть языком кодирования начального уровня, который все еще может эффективно использоваться экспертом).
Тем не менее, я очень легко могу выполнить эту операцию "путем передачи по ссылке", заставив вместо этого изменить память (которая не копируется, когда я отправляю x в функцию):
>>> def refpow2(x):
... x *= x
...
>>> refpow2(x)
>>> x
array([[1., 4.],[4., 4.]])
Итак, вы видите, что python можно немного уловить, чтобы делать то, что вы пытаетесь сделать.