我一直在尝试跟踪Python代码的内存使用情况,并担心Python函数是通过引用还是从函数空间复制到上/全局空间。
e.g。
def f1():
a = range(10000) # or np.arange(10000)
return a
def f2():
a = range(10000)
for i in range(10):
yield a[1000*i : 1000*(i+1)]
如果我调用b = f1()
,并且在函数内创建了a
,则会为b
分配对a
对象的引用,或者是{a
指向的对象1}}复制然后由b
引用,然后在函数调用结束时删除a
以及a
的对象?
同样,如果我执行以下操作,
for a_slice in f2():
b = a_slice
也是“只创建一次”或“复制到全球空间”的产生对象? NumPy数组的行为与Python列表相同吗?
答案 0 :(得分:3)
第一个问题,你可以做一个简单的测试:
>>> def f1():
a = range(10) # or np.arange(10000)
return a, id(a)
>>> b, id_b = f1()
>>> id(b) == id_b
True
因此,b
指向a
指向的完全相同的对象。它没有被复制。
对于第二种情况,我们可以做同样的事情:
>>> def f2():
a = range(100)
for i in range(10):
tmp = a[i:i**2]
yield tmp, id(tmp)
>>> b = f2()
>>> for tmp, id_tmp in b:
print id_tmp == id(tmp)
True
True
True
True
True
True
True
True
True
True
似乎它与第一种情况完全相同。好吧,这是python。我们正在做的所有事情都是关于参考文献:)
希望这有帮助!
答案 1 :(得分:2)
如果您评估表达式b = f1()
,那么您通过调用b
将名称list
与f1()
内创建的range()
实例绑定在一起
Python就是所有引用。创建list
对象,并从函数返回对该对象的引用,然后将引用绑定到名称b
。
您的函数f2()
生成一个生成器,它将首先创建一个包含10000个整数的list
实例,并将私有变量名a
与对该list
的引用绑定在一起实例。然后,当您从迭代器中提取值时,循环中的每个切片操作都将创建一个新的list
实例,该实例将被放弃。一旦循环完成并且已经生成了最后一个list
实例,将清除生成器,此时列表a
将不再使用并将被垃圾收集。 (在CPython中,垃圾收集基于引用计数,并且对于这种情况将非常迅速地工作。对于其他版本的Python,例如Jython或PyPy,垃圾收集的可预测性要低得多。)
我不是NumPy专家,但我的理解是array
个实例的“视图”(包括切片)应该占用很少的内存。他们不会复制原始数据。如果您更改f2()
以使用numpy.array()
构建numpy.arange()
实例,然后生成切片,我预测您的程序将使用更少的内存。 f2()
的当前实现创建并销毁10个list
个实例,列表a
的切片; NumPy array
切片应该避免这种情况。
我刚刚测试了以上内容:
import numpy as np
a = np.arange(100)
b = a[0:3]
b[0] = 99
assert a[0] == b[0]
在此示例中,b
是数组a
的“视图”。它没有分配新的列表或数组,正如通过分配给b[0]
来改变数组所证明的那样。 a[0]
的值也会发生变化,因为b
只是同一数组的另一个视图。
(任何NumPy专家,如果我在这里犯了任何错误,请指出。谢谢。)