NumPy数组是通过引用传递的吗?

81

我注意到在多个地方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。


1
相关:https://dev59.com/PWox5IYBdhLWcg3wq2IC - Fred Foo
Python 中所谓的“引用”与传递引用并没有任何关系,这就是为什么。 - user395760
3个回答

96
在Python中,所有变量名都是值的引用
当Python执行赋值操作时,右侧先被计算,然后才是左侧arr - 3会创建一个新的数组;它不会就地修改arr

arr = arr - 3会使本地变量arr引用这个新数组。它不会修改最初由arr传递给foo的引用值。变量名arr只是绑定到新数组arr - 3。此外,arrfoo函数作用域内的局部变量名。一旦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)

相比之下,Python将arr -= 3转换为调用__iadd__特殊方法,会就地修改arr所引用的数组。

因此,为了使 foo() 产生效果,它需要返回 arr,并且在代码中运行它时使用 a = foo(a) - Reti43
4
等等,Python中的对象在函数中是按引用传递的吗? - jlcv
1
Python既不是传递引用也不是传递值。有些人称之为“对象调用”。请参阅文档中的脚注。由于这个术语并不是很常见,因此描述和命名它是重要且必要的。 - unutbu

10

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(Basic Linear Algebra Subprograms)事件都将涉及“读取”步骤,在此步骤中,要么形成一个新数组,用户(本例中的代码编写者)知道这一点,要么形成一个在临时变量中“隐藏”的新数组,用户并不知道这一点(您可能会看到这样的 .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可以进行一些微调来实现你想要的功能。


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接