由于没有系统的学习过Python,许多知识点都是破碎的,无法形成一个整体。总喜欢用其他语言来类比Python的语法。例如,对于参数传递,总觉得Python也会像C++那样分为值传递与引用传递。无意间看到一篇博文,才发现其实区别很大。
首先,Python的函数参数传递,没有值传递和引用传递之分,因为根本没有引用传递的调用方式。
其次,就算都是值传递,Python的值传递也跟C++的值不一样。C++的值传递方式,实参的值是不会在函数调用之后有任何改变的,这点毫无疑问。而Python的值传递方式(这里姑且还是这样称呼它为值传递
),实参在函数调用之后,可能不变,也可能改变,来看下面的例子:
# 例子1
def test_int(input_value):
input_value = 2
one_value = 1
test_int(one_value)
print('one_value = {}'.format(one_value))
例子1输出为:one_value = 1
,例子1的输出很好理解,那下面的例子2呢?
# 例子2
def test_list(input_list):
input_list.append(3)
one_list = [1, 2]
test_list(one_list)
print('one_list = {}'.format(one_list))
例子2输出为:one_list = [1, 2, 3]
,为何结果会不一样呢?
其实,在 Python中,类型属于对象,变量是没有类型的,变量只是指向了对象:参数传递后,变量one_value和input_value都指向对象1,可以参考下面的贴图:
(2和3这些对象都是系统为了效率提前产生的)。由于test_int()
函数体内要将变量input_value
指向对象2,然后对象1不能直接转换成对象2(可以理解为常量对象),那么只能改变变量input_value的指向,使其指向对象2,那么整个对象结构就会变成如下形式:
因此,函数调用返回时,变量one_value
和input_value
指向了不同的对象。所以变量one_value的值保持不变。例子2中,变量one_list
和input_list
都指向同一个list对象:
注意list对象本身是可以改变的,向其加入一个元素不会重新创建对象,是直接在原对象添加新元素。因此调用test_func2()
后,该对象结构如下图:
one_list
和input_list
依旧指向同一对象,改变input_list
指向对象的值也就间接的改变了one_list
的指向对象的值。
归根结底,函数调用后,如果形参转向指向另外的对象,形参的改变就不会影响到实参;如果形参的改变是直接在原指向对象上进行操作,形参实参任然都会指向同一对象,形参的改变就会同步到实参上。
或者说,函数的传参,应该分为不可变对象传递
和可变对象传递
,strings, set, tuples, numbers
是不可更改的对象,而list,dict
等则是可以修改的对象。函数调用为不可变对象传递时,即使函数体中修改了形参,实参的值在函数调用返回时保持不变;如果函数调用为可变对象传递,函数体中修改了形参,实参的即会跟随发生改变。
弄懂了上面的原因,下面的情况也就很好理解了:
# 例子3
def test_tuple(input_tuple):
input_tuple += (3, 4)
one_tuple = (1, 2)
test_tuple(one_tuple)
print('one_tuple = {}'.format(one_tuple))
例子3的输出为one_tuple = (1, 2)
。