69

我遇到了这样一个事实,即numpy数组在多个地方通过引用传递,但是当我执行以下代码时,为什么foobar

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 版本 1.6.1

4

3 回答 3

84

在 Python 中,所有变量名都是对 values 的引用

当 Python 计算一个赋值时,右边的值在左边的值之前计算arr - 3创建一个新数组;它不会arr就地修改。

arr = arr - 3使局部变量arr引用这个新数组。它不会修改arr传递给的最初引用的值foo。变量名称arr只是绑定到新数组arr - 3. 而且,arr是函数作用域中的局部变量名foo。一旦foo函数完成,就不再有引用arr,Python 可以自由地垃圾收集它引用的值。正如 Reti43 指出的那样,为了arr影响 的值afoo必须返回arr并且a必须分配给该值:

def foo(arr):
    arr = arr - 3
    return arr
    # or simply combine both lines into `return arr - 3`

a = foo(a)

相比之下,arr -= 3Python 将其转换为对__iadd__特殊方法的调用,它确实修改了原地引用的数组arr

于 2012-07-20T19:34:57.560 回答
9

第一个函数计算(arr - 3),然后为其分配本地名称arr,这不会影响传入的数组数据。我的猜测是,在第二个函数中,np.array覆盖了-=运算符,并在数组数据上进行了操作。

于 2012-07-20T19:35:38.420 回答
8

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 是通过引用传递的,但也许不是?-- 不,在这里,我们用一个全新的声明(通过 python 中的解释隐藏)隐藏了 x,并且 python 不会将这种“阴影”传播回全局范围(这将违反 python 用例:即,成为初学者仍然可以被专家有效使用的编码语言)。

但是,我可以通过强制修改内存(当我将 x 提交给函数时不会复制)来以“按引用传递”的方式非常轻松地执行此操作:

>>> def refpow2(x):
...    x *= x
...
>>> refpow2(x)
>>> x
array([[1., 4.],[4., 4.]])

所以你看到python可以做一些你想做的事情。

于 2018-12-15T22:25:11.900 回答