关于python2与python3中的位置参数,可变参数,默认参数,关键字参数
参考:
https://blog.csdn.net/Alian_W/article/details/105131376
https://www.jianshu.com/p/98f7e34845b5
1. 位置参数 (Positional)
位置参数顾名思义就是与位置有关的参数,在调用函数的时候需要根据传入参数的位置来一一对应参数。并且,位置参数是不可缺省的,即不传入完整的参数是会报错的。位置参数是最为常见的一种参数,例如:
1 | def add(num1, num2): |
add 函数的两个参数即为位置参数,调用时第几个位置传入,则对应函数定义中该位置的参数。
小结:位置参数就是与函数参数位置有关的参数,且函数的位置参数不可缺省,必须传满
2. 默认参数 (Default)
默认参数顾名思义就是与默认值有关的参数,为了增加函数的鲁棒性,也为了传参时在可以使用默认设置的情况下不用传那么多参数,参数可以设置默认值,这样即使缺省了该参数也不会报错。例如:
1 | def minus(num1, num2=0): |
注意:定义函数时默认参数一定要在位置参数之后。其实很好理解,若默认参数可以放在位置参数之前,那当传参的个数少于参数个数,解释器到底是将参数给位置参数还是给默认参数?如下所示:
1 | def minus2(num1=1, num2): |
小结:默认参数就是需要设置默认值的参数,默认参数必须放在位置参数之后,当需要使用默认值时可以不传默认参数
3. 可变参数
在定义函数时有可能出现不确定需要传递的参数的个数的情况,这时可变参数将会给我们带来极大的便利。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
示例1:
1 | def fun(num1, num2=1, *args): |
函数定义时, *args
表示把传进来的位置参数都装在元组
args
里面;函数内部的*args
表示拆包。
从函数定义中参数的位置来看,从左到右依次是位置参数、默认参数、可变参数(*args)
。(Python2中必须按这个顺序传,Python3中默认参数可以在可变参数之后,具体示例见后文)
可以注意到,按上面位置参数、默认参数、可变参数的顺序来定义函数时,在调用时,如果想传入可变参数,必须先传入默认参数。如上面的示例,2被传给num2
,(3,4,5)被传给args
,就算我们想使用num2
的默认值1,也必须在调用时写明。例如我们想给num2
传入1,给args
传入(2,3,4,5),需写作
fun(1,1,2,3,4,5)
,如果省略,写作fun(1,2,3,4,5)
,则会导致2被传给num2
。猜测这也是为什么Python3中允许函数定义时将可变参数写在默认参数前,如此默认参数如果使用默认值则无需传入。
示例2:以一道数学题为例,给定一组数字 \(a,b,c\), …,请计算 \(a^2 + b^2 + c^2 +\) …
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:
1 | def calc(numbers): |
调用的时候,需要传入一个list或tuple:
1 | 1, 2, 3]) calc([ |
如果利用可变参数,函数的定义和调用方式如下:
1 | def calc(*numbers): |
1 | 1, 2, 3) calc( |
函数定义的差别,仅仅在参数前面加了一个*号,函数内部,numbers
接收到的是一个tuple,函数代码完全不变。函数调用的差别,由原来传入一个tuple或list变为传入单个数。调用该函数时,可以传入任意个参数,包括0个参数:
1 | 1, 2) calc( |
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
1 | 1, 2, 3] nums = [ |
这种写法当然是可行的,但是太繁琐,所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
1 | 1, 2, 3] nums = [ |
*nums
表示把nums
这个list的所有元素作为可变参数传进去(拆包)。这种写法相当有用,而且很常见。
小结:*args传递参数的时候会被封装成一个元组形式,可以通过拆包的方式获取里面的参数。
4. 关键字参数 (Keyword)
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple,而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
1 | def fun(num1, num2=1, *args, **kwargs): |
直接打印kwargs
可以看到传来的是一个字典,即前面的3 4 5
都丢进了*args
里,而**kwargs
装的是没有对应上的关键字参数。通过拆包我们看到里面包含的关键字有name,
age,通过字典的方式可以获取关键字对应的值。
示例2:
1 | def person(name, age, **kwargs): |
函数person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数,也可以传入任意个数的关键字参数:
1 | 'Michael', 30) person( |
关键字参数有什么用?它可以扩展函数的功能。比如,在person
函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict
,然后,把该dict
转换为关键字参数传进去:
1 | 'city': 'Beijing', 'job': 'Engineer'} extra = { |
当然,上面复杂的调用可以用简化的写法:
1 | 'city': 'Beijing', 'job': 'Engineer'} extra = { |
**extra
表示把extra
这个dict
的所有key-value
用关键字参数传入到函数的**kwargs参数
,kwargs将获得一个dict。注意kwargs获得的dict是extra的一份拷贝,对kwargs的改动不会影响到函数外的extra。
5. Python2 与 Python3 中参数定义顺序的差异
5.1 函数定义中的*与**
参考
python2和python3中的可变参数有什么不同?
https://www.zhihu.com/question/21099657
python2中参数定义的顺序必须是:位置参数、默认参数、可变参数和关键字参数。python3中不一定要如此。
例:
1 | #Python2中只能按如下方式定义 |
调用:
1 | # Python2 |
值得注意的是在调用的时候,positional argument cannot appear after keyword arguments(python2, python3 中均如此)
1 | func1(1,b=2,3,4,x=5,y=6) |
↑在python2与python3中运行结果一致,都会报错,即调用时,出现了某参数=某值时,后面的参数均要加上参数名。(也即func1若想改变默认参数的取值,不能使用b=2
,而只能在对应位置写上2)
这也是为什么在Python3中更推荐使用函数参数定义顺序:位置参数,可变参数,默认参数,关键字参数(也即上面func2的写法)。调用时:
1 | func2(1, 3, 4, b=2, x=6, y=7) |
另一个例子:
1 | def concat1(*args, sep="/"): |
↑此定义在python2中会报错(默认参数应在可变参数前),在python3中不会报错。在python2中的正确写法:
1 | def concat2(sep="/", *args): |
现在,想通过"."
把"aaa"
,"bbb"
,"ccc"
连接起来。调用时:
1 | concat1("aaa","bbb","ccc",sep=".") |
5.2 函数调用中的*与**
假设有函数:
1 | def test(a,b,c): |
test(*args)
:*
的作用其实就是把args
中的每个元素,作为位置参数传进去。
test(**kwargs)
:**
的作用则是把
kwargs
变成关键字参数传递。
例1:
1 | args = (1,2,3) |
例2:
1 | kwargs = {'a':1, 'b':2, 'c':3} |
例3:
1 | def test(*args): |
另一个例子:
1 | def super_func(*args): |
1 | def super_func(*args, **kwargs): |
关于,可变参数与默认参数,再来看一个更复杂的例子,先来看下面这段源码,来自于pyplot的barbs函数:
1 |
|
data=None
作为默认参数是可以放在*args
后面的,那么我们仿一下这个函数,看看它传进来的值都是怎么赋值的。
1 | def fun2(*args, data=None, **kwargs): |
1 2 3 4都传给了*args
;data=123
作为关键字参数传给了data=None
从而后面的kwargs
就不能再有data
这个关键字了,否则会报重复关键字错误;在确定后面没有默认参数后,将接下来的关键字参数都丢给了**kwargs
接着上面的源码,它调用了gca().barbs
并返回这个函数的返回值,来看看barbs
这个函数是怎么接收参数的:
1 | def barbs(self, *args, **kw): |
那么再来仿一个函数:
1 | def fun2(*args, data=None, **kwargs): |
可以看到*args
的参数直接丢给fun3
了,但是data
做了一个简单的判断封装成了字典,跟着**kwargs
一起丢给fun3
的**kwargs
从这个例子能学到什么呢?
首先默认参数也是可以放在*args
后面,因为编译器可以识别出后面的关键字参数已经不是*args
的内容了,接下来遇上了默认参数,一一赋值给关键字参数,剩下的再给**kwargs
就可以了。
其次,**kwargs
是可以接收多个字典并且封装成一个大字典的,他的过程就是将所有关键字参数、字典都拆解然后再重新封装成字典的过程,最终在fun3
中接收到的就是一个大字典。
6. 题外话:parameter和argument的区别
- parameter是指函数定义中参数,而argument指的是函数调用时的实际参数。
- 简略描述为:parameter=形参(formal parameter), argument=实参(actual parameter)。
- 在不很严格的情况下,二者可以混用,一般用argument,而parameter则比较少用。
A parameter is an intrinsic property of the procedure, included in its definition. By contrast, the arguments are the values actually supplied to the procedure when it is called. Unlike the parameters, which form an unchanging part of the procedure’s definition, the arguments can, and often do, vary from call to call.