Python学习之参数(一)
文章目录
- Python学习之参数(一)
- 参数的传递
- 避免可变参数的修改
- 参考资料
参数的传递
所有的参数实际上都是通过指针进行传递的。作为参数被传递的对象从来不自动拷贝。
在函数内部的参数名的赋值不会影响调用者。
如果传入的是可变对象,那么在函数内就可以就地改变这个可变对象,这可能会影响调用者。
Python通过赋值进行传递的机制与C++的引用参数选项并不完全相同,实际上它与C语言的参数传递模型相当相似。
不可变参数“通过值”进行传递。像整数和字符串这样的对象是通过对象引用而不是拷贝进行传递的,但是因为你无论怎样都不可能在原处改变不可变对象,所以实际效果就像创建了一份拷贝。
可变对象是通过指针进行传递的。例如,像列表和字典这样的对象也是通过对象引用进行传递的,这一点与C语言使用指针传递数组很相似:可变对象能够在函数内部进行原处改变,这一点和C数组很像。
以上只是偏重于理论的叙述,如果想搞清楚Python的参数传递机制,恐怕要研究几个例子。
考虑如下代码:
>>> def f(a): # a is assigned to (references) the passed object
a = 99 # Changes local variable a only
>>> b = 88
>>> f(b) # a and b both reference same 88 initially
>>> print(b) # b is not changed
88
第3行,b引用了88,可以认为b指向了88;
第4行,调用函数f,把b传了进去,实际上传递的是一个指针;
第1行,这里的a拥有了b的值,实际上是a也指向88;
第2行,a又指向了99;
第5行,打印b,此时b已然指向88,所以结果是88。
咱们再看一个例子。
>>> def changer(a, b): # Arguments assigned references to objects
a = 2 # Changes local name's value only
b[0] = 'spam' # Changes shared object in place
>>> X = 1
>>> L = [1, 2] # Caller:
>>> changer(X, L) # Pass immutable and mutable objects
>>> X, L # X is unchanged, L is different!
(1, ['spam', 2])
从第8行的结果来看,X 并没有改变,但是 L 从 [1, 2] 改变为 [‘spam’, 2]
X 没有改变,这和第一个例子是同样的道理。
我们分析一下 L 为什么会改变。第1行的b也是一个本地变量,但是一个可变对象传给了它——b指向了一个列表,它和L共享同一个对象。第3行,对b[0]进行赋值的结果会在函数返回后影响L的值。
下图中说明了函数调用前和调用中变量名与对象的绑定关系。
在调用changer函数之前,X 指向 1,L 指向列表 [1, 2] ;在函数调用的时候,X的值(对象1的地址)被传递给 a,所以 a 也指向1,同理,b 指向列表 [1, 2] ;因为 1 是不可变对象,所以 a = 2 并不会修改对象 1,而是使 a 指向 2,但是列表是可变对象, b[0] = ‘spam’ 确实修改了列表 [1, 2] ,使其变成了 [‘spam’, 2]。
如果上面的内容依然令你困惑的话,你可以这么理解,通过函数调用进行参数赋值与运行下面一系列简单的赋值语句是等效的:
>>> X = 1
>>> a = X # They share the same object
>>> a = 2 # Resets 'a' only, 'X' is still 1
>>> print(X)
1
>>> L = [1, 2]
>>> b = L # They share the same object
>>> b[0] = 'spam' # In-place change: 'L' sees the change too
>>> print(L)
['spam', 2]
避免可变参数的修改
对可变参数的原处修改的行为不是一个 bug——它只是参数传递在Python中的工作方式。在Python中,默认通过引用(也就是指针)进行参数传递,因为这通常是我们想要的。这意味着不需要创建多个拷贝就可以传递很大的对象,并且能够按照需要方便地更新这些对象。
如果不想要函数内部在原处的修改影响到传递给他的对象,那么可以创建一个原对象的拷贝。例如:
L = [1, 2]
changer(X, L[:]) #通过切片传递拷贝,所以L不会改变
如果不想改变传入的对象,无论函数是如何调用的,我们同样可以在函数内部进行拷贝。
def changer(a, b):
b = b[:] # Copy input list so we don't impact caller
a = 2
b[0] = 'spam' # Changes our list copy only
—未完待续—
参考资料
《Python学习手册(第4版)》,机械工业出版社
《Learning Python 5th Edition》, O’Reilly