【 Python 基础 】S01E10 函数参数的传递、修改、匹配与解包过程全解析

0.本集概览

1.函数参数传递的实现过程
2.可变对象和不可变对象参数传递、修改的区别
3.如何避免参数传递的本地修改
4.基于位置和关键字的参数匹配方法
5.使用默认参数形式
6.函数定义使用* 和** 进行任意数目参数收集
7.函数调用时使用* 和** 进行参数解包

今天我们再来说说函数中的参数传递问题。这个看上去自然而然的过程里其实很有讲究。

1.参数的传递

参数的传递是通过自动将对象赋值给本地变量名来实现的。在函数运行时,函数头部的参数名是一个新的、本地的变量名,这个变量名是在函数的本地作用域内存在。参数的传递本质上就是Python赋值的另一个实例而已。

2.参数的修改

那么,这个问题分为可变对象和不可变对象两种情况进行讨论:

在原处改变函数的可变对象参数的值会对调用者有影响。函数能够就地改变传入的可变对象,因此其结果会影响调用者,这其实和前面介绍过的对象赋值原理是一样的;

而不可变对象的引用重新赋值会指向新的对象,因此不会改变调用者。

2.1.不可变对象参数的修改

先看一个不可变对象参数的例子
代码片段:

def f(a):
    a = 99
    print(a)

b = 88
f(b)
print(b)

运行结果:

99
88

在函数中修改a对于调用函数的地方没有任何影响,因为他在函数内部直接把本地变量a重置为了一个完全不同的新对象,所以他不会影响最初的变量b

2.2.可变对象参数的修改

而当参数传递像列表和字典这样的可修改对象的时候,我们还需要注意,对这样的可变对象的原处修改可能在函数退出后依然有效,并由此影响到调用者。
代码片段:

def change(a,b):
    a = 2
    b[0] = 'spam'

x = 1
l = [1,2]
change(x,l)
print(x,l)

运行结果:

1 ['spam', 2]

再次对比可以看出,调用者的不可变变量x没有受到影响,而可变变量L在函数内部进行本地修改,并影响到了自身

可以看出,对于参数a,仅仅把本地变量a修改为引用一个完全不同的对象,并没有改变调用者作用域中的名称x的绑定。而参数b被传给了一个可变对象(在调用者作用域中叫做L的列表),因为第二个赋值是一个在原处发生的对象改变,对函数中b[0]进行赋值的结果会在函数返回后影响L的值,他修改了b所引用的对象的一部分,因为引用共享对象的缘故,L也被同时改变。

再强调一次,其实参数传递后的本地修改过程和简单对象赋值后的对象修改,实质上是一回事,换句话说就等于下面这个例子所描述的程序过程:
代码片段:

L = [1,2]
b = L
b[0] = 'spam'
print(L)

运行结果:

['spam', 2]

2.3.避免可变对象原处修改的方法

实际上,可变参数的原处修改行为并不是一个bug,它只是参数传递在Python中工作的方式。在Python中,默认通过引用进行函数的参数传递,是因为这通常是我们所想要的:这意味着不需要创建多个拷贝就可以在我们的程序中传递很大的对象

如果不想要函数内部在原处的修改影响传递给它的对象,那么,我们可以简单的创建一个明确的可变对象的拷贝:
代码片段:

def change(a,b):
    a = 2
    b[0] = 'spam'

x = 1
l = [1,2]
change(x,l[:])
print(x,l)

运行结果:

1 [1, 2]

或者在函数内部进行拷贝,这样可以不改变传入的对象,函数调用看上去没有变化
代码片段:

def change(a,b):
    b = b[:]
    a = 2
    b[0] = 'spam'

x = 1
l = [1,2]
change(x,l)
print(x,l)

运行结果:

1 [1, 2]

3.参数传递时的匹配

正如我们之前所讲的,参数在python中总是通过赋值进行传递的。在默认情况下,参数是通过其位置进行匹配的,从左到右,而且必须精确的传递和函数头部参数名一样多的参数。

3.1.默认基于位置的匹配

这种默认的传递方式很简单
代码片段:

def f(a,b,c):
    print(a,b,c)

f(1,2,3)

运行结果:

1 2 3

3.2.基于关键字的参数匹配

Python中可以使用基于关键字的参数匹配形式。在调用函数的时候,能够更详尽的定义内容传递的位置。关键字参数允许通过变量名进行匹配,而不是通过位置。
代码片段:

def f(a,b,c):
    print(a,b,c)

f(c=3,a=1,b=2)

运行结果:

1 2 3

我们可以看出,当关键字参数使用时参数从左至右的关系已不再重要了,因为参数是通过变量名进行传递的,而不是通过其位置。这种调用显得更文档化一些(例如使用一些名称更直观的参数名)。

3.3.混合方式的匹配

甚至在一个调用中混合使用基于位置的参数和基于关键字的参数也可以。在这种情况下,所有基于位置的参数首先按照从左到右的顺序匹配头部的参数,之后再进行基于变量名进行关键字的匹配。
代码片段:

def f(a,b,c):
    print(a,b,c)

f(1,c=3,b=2)

运行结果:

1 2 3

3.4.默认参数及使用举例

再来说说Python中的默认参数形式。默认参数允许创建函数可选的参数,如果没有传入值的话,在函数运行前,参数就被赋予了默认值,还是看一个例子:
代码片段:

def f(a,b=2,c=3):
    print(a,b,c)

f(1)
f(a=1)

运行结果:

1 2 3
1 2 3

看到这个例子中,我们必须为a提供值,无论是通过位置参数还是关键字参数来实现,然而,为b和c提供值是可选的。如果我们不给b和c传递值,它们会分别赋值为2和3。

那么按位置顺序,当给函数传递两个值的时候,只有c得到默认值,并且当有三个值传递时,不会使用默认值。
代码片段:

def f(a,b=2,c=3):
    print(a,b,c)

f(1,4)
f(1,4,8)
top Created with Sketch.