Python函数究竟如何返回/生成对象?

时间:2014-01-09 03:10:07

标签: python python-2.7 numpy

我一直在尝试跟踪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列表相同吗?

2 个答案:

答案 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将名称listf1()内创建的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专家,如果我在这里犯了任何错误,请指出。谢谢。)