今天看到这样一个问题: Python 的函数是怎么传递参数的,有了一些兴趣,因为以前都是直接信的一个流传度较广的说法
对于不可变对象作为函数参数,相当于C系语言的值传递;
对于可变对象作为函数参数,相当于C系语言的引用传递。
那么事实上真是如此吗?我们来看几个例子。
首先我们来看看第一个说法,这里我实验环境是Python3.6。
对于不可变对象作为函数参数进行传递,这里我分别以int
和tuple
进行测试
def test_immutable_for_simpledata(args):
print('============')
print(2, id(args))
args = args + 10
print(3, args)
print(4, id(args))
print('============')
if __name__ == '__main__':
cur = 5000
print(1, id(cur))
test_immutable_for_simpledata(cur)
print(5, cur)
print(6, id(cur))
结果如下
1 42301856
============
2 42301856
3 5010
4 42301616
============
5 5000
6 42301856
我们看第一步和第二步,两者打印出来的地址是一样的,这可以证明Python对不可变类型传参是使用的引用传参,但是为什么函数中用了args+10
,函数外面的args
值还是没变呢?我们可以看到第四步的args
的地址其实已经变了。即说明加10和未加10两个时间点是两个不同的对象。为什么是这样呢?这就需要理解什么是不可变对象了。在我理解来,不可变对象就是它的成员元素不变,比如对于简单对象(如int
,float
)来说,就是它的值不变,对于复合类型(如tuple
)来说,就是它的成员元素(即地址)不变。回到代码中,这里是基本类型的不可变对象,对它进行运算操作其实就是创建新的对象,然后将原先的变量名绑定到新的对象上。读者前一句话没读懂的话,可以去了解一下Python的名字空间。
为了加深读者的理解,我们再以tuple
为例进行测试。代码如下
def test_immutable_for_tuple(args):
print('============')
print(2, id(args))
args[0].append(5)
print(3, args)
print(4, id(args))
print('============')
if __name__ == '__main__':
cur = ([0], 1)
print(1, id(cur))
test_immutable_for_tuple(cur)
print(5, cur)
print(6, id(cur))
结果如下
1 33951020
============
2 33951020
3 ([0, 5], 1)
4 33951020
============
5 ([0, 5], 1)
6 33951020
这次的结果和上次明显不同了。虽然tuple
是不可变对象,我们使用tuple
为参数传入函数中,然后对tuple中的列表元素进行了操作,结果函数外部的变量也发生了变化。我们也可以看到参数的地址在函数内外其实都一样。
根据上述测试,我们可以得出一个结论,对于不可变对象的函数传参,依然是传的引用(地址)。对于简单类型,在函数内对其操作之所以不会影响函数范围外的值,是因为运算中的赋值操作产生了新的对象,而不是对原有对象的改变。而对于一些复杂类型,就可以比较清晰的看出,函数内参数的改变同样会影响函数外的变量。
对于可变对象,我们同样可以使用上述逻辑去验证,由于篇幅,我就不啰嗦了。
最终可以得到的结论是,Python中的函数通过引用传参。