Подтвердить что ты не робот

Являются ли массивы 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

4b9b3361

Ответ 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 можно немного уловить, чтобы делать то, что вы пытаетесь сделать.