python参数详解

default, position, vrags, kwvargs, keyword-only

Posted by rpl on April 19, 2019

前言

在开始讨论python各种类型的参数前,先介绍一个新知识inspect模块, 这个模块可以很好的展示出一个函数的参数信息,如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from inspect import signature

def func(a,b,*var, c, d, e=3, **kw):
    pass
    
sig = signature(func)

sig
<Signature (a, b, *var, c, d, e=3, **kw)>

str(sig)
'(a, b, *var, c, d, e=3, **kw)'

for name, param in sig.parameters.items():
    print('参数类型:%s, 参数名:%s, 参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,    参数名:a, 参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,    参数名:b, 参数默认值:<class 'inspect._empty'
# 参数类型:VAR_POSITIONAL,           参数名:var, 参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,             参数名:c, 参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,             参数名:d, 参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,             参数名:e, 参数默认值:3
# 参数类型:VAR_KEYWORD,              参数名:kw, 参数默认值:<class 'inspect._empty'>

inspect.signature 函数返回一个 inspect.Signature 对象,它有一个 parameters 属性,这是一个有序映射,把参数名和 inspect.Parameter 对象对应起来。各个 Parameter 属性也有自己的属性,例如 name、default 和 kind。特殊的inspect._empty 值表示没有默认值,考虑到 None 是有效的默认值(也经常这么做),而且这么做是合理的。

kind 属性的值是 _ParameterKind 类中的 5 个值之一,列举如下

POSITIONAL_OR_KEYWORD: 可以通过定位参数和关键字参数传入的形参(多数 Python 函数的参数属于此类)

VAR_POSITIONAL: 定位参数元组

VAR_KEYWORD: 关键字参数字典

KEYWORD_ONLY: 仅限关键字参数(Python 3 新增)


下面我们正式开始介绍python的各种参数

1.位置参数 positional

位置参数是按位置顺序传参的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def func(a,b,c):
    print(a,b,c)
 
# 如果不指定关键字,形参a, b, c绑定的值, 完全取决于实参传入的顺序   
func(1,2,3)
1 2 3
func(3,2,1)
3 2 1

sig = signature(func)
for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:b,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:c,  参数默认值:<class 'inspect._empty'>

2. 默认参数,关键字参数 default, keyword

函数定义时,如果有参数,则所有的形参名都是一个关键字,如果想通过关键字的方式传参,可以使用 关键字=值得方式传参,默认参数是在函数定义时就给参数传入了一个默认的参数值,如果函数调用时没有给这个参数传值,就使用默认值, 如果显式的传参了,就使用新传入的值代替默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def func(a,b, c='xxx'):  # c在函数定义时就传入了一个默认值,c就是默认参数
    print('a=', a)
    print('b=', b)
    print('c=', c)
    
func(1,2)   # 调用func函数时,没有给c指定传入的值,c就使用默认参数值
a= 1
b= 2
c= xxx
func(1,2, c='000')  # c指定传入值得话,使用指定值
a= 1
b= 2
c= 000
func(c='000', a='111', b='222')  # 使用关键字传参,就无需在意参数的位置顺序
a= 111
b= 222
c= 000

sig = signature(func)
for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:b,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:c,  参数默认值:xxx

3. 可变参数 var_positional

可变参数是一个形参可以接受多个实参, 用*varargs标志这是一个可变参数。可变参数的传入数是不决定的,通常由函数调用方决定。可变参数会将所有接受的值以元组的形式传入函数体内,如果可变参数没有接受到任何值,则传入一个空元组(,)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def func(a,b,*var,):
    print('a=', a)
    print('b=', b)
    print('var=', var)
    
func(1,2,3,4,5)   # a,b填满后, 剩下的都传给可变参数
a= 1
b= 2
var= (3, 4, 5)

func(1,2,)   
a= 1
b= 2
var= ()  # 可变参数没有收到任何值,就是个空元组

sig = signature(func)
for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:b,  参数默认值:<class 'inspect._empty'>
# 参数类型:VAR_POSITIONAL,                参数名:var,  参数默认值:<class 'inspect._empty'>

值得一提的是,函数调用时各种参数的传入是有接受顺序的,如下, 4,5,6这三个实参传入时,位置参数会被优先赋值, 4,5会分别传给a, b。 接下来(6,7)传给var, 之后又出现a=1 这种关键字传参,因为a已经被赋过值了, 所以报错。关于函数传入顺序的具体描述, 可以点击查看这篇文章:参数调用行为,这里不在详解。

1
2
3
4
5
6
func(4,5,6,7 a=1,b=2)
Traceback (most recent call last):
  File "/usr/lib/python3.6/code.py", line 91, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
TypeError: func() got multiple values for argument 'a'

4. 可变关键字参数 var_keyword

可变关键字参数 用 双星号+参数名表示, 如:**kwargs,可变关键字参数接收零个或多个关键字参数,并以字典的形式传入函数体,关键字为此字典的key,关键字绑定的值为value。如果可变关键字没有接收到任何参数, 则传入函数体一个空字典{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def func(a,b,*var, **kw):
    print('a=', a)
    print('b=', b)
    print('var=', var)
    print('kw=', kw)
    
func(1,2,3,4, x1=11,x2=22,x3=33)  # 传入多个关键字参数
a= 1
b= 2
var= (3, 4)
kw= {'x1': 11, 'x2': 22, 'x3': 33}

func(1,2,3,4, **{'y1':11, 'y2':22})   # 使用 ** 解包字典
a= 1
b= 2
var= (3, 4)
kw= {'y1': 11, 'y2': 22}

func(1,2, **{'y1':11, 'y2':22})
a= 1
b= 2
var= ()
kw= {'y1': 11, 'y2': 22}

func(1,2,)   # 可变关键字参数,没有接收到任何值,就返回一个空字典
a= 1
b= 2
var= ()
kw= {}

for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:b,  参数默认值:<class 'inspect._empty'>
# 参数类型:VAR_POSITIONAL,                参数名:var,  参数默认值:<class 'inspect._empty'>
# 参数类型:VAR_KEYWORD,                   参数名:kw,  参数默认值:<class 'inspect._empty'>

5. 仅限关键字参数 keyword_only

仅限关键字参数就是只能传入关键字参数,不能通过其他方式传参。仅限关键字参数不可缺省(除非有默认值),且只能强制性通过关键字传参。 在 定义参数时,若想指定仅限关键字参数,要把它们放在有的可变参数后面。即可变参数后面的关键字参数都是仅限关键字参数, 若不想要可变参数,只要仅限关键字参数,可以省略可变参数名,只留一个单星号*表示不接受任何可变参数,它可以作为普通参数的结束标志, 如下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def func(a,b, *args, e, f, g='xxx', **kw):
    print('a=', a)
    print('b=', b)
    print('args=', args)
    print('e=', e)
    print('f=', f)
    print('g=', g)
    print('**kw', kw)
    
sig = signature(func)
for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:POSITIONAL_OR_KEYWORD,         参数名:b,  参数默认值:<class 'inspect._empty'>
# 参数类型:VAR_POSITIONAL,         参数名:args,  参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,         参数名:e,  参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,         参数名:f,  参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,         参数名:g,  参数默认值:xxx
# 参数类型:VAR_KEYWORD,         参数名:kw,  参数默认值:<class 'inspect._empty'>

func(1,2,3,4, e='a',f='b',g='c', m='123', n='456')

a= 1
b= 2
args= (3, 4)
e= a
f= b
g= c
**kw {'m': '123', 'n': '456'}

def func(*, a, b):
    print(a, b)
    
sig = signature(func)
for name, param in sig.parameters.items():
    print('参数类型:%s,         参数名:%s,  参数默认值:%s' %(param.kind, name, param.default))
    
# 参数类型:KEYWORD_ONLY,         参数名:a,  参数默认值:<class 'inspect._empty'>
# 参数类型:KEYWORD_ONLY,         参数名:b,  参数默认值:<class 'inspect._empty'>

func(a=1,b=2)  # 必须强制通过关键字传参
1 2

func(1,2)  # * 表示普通参数结束位, 再传入位置参数会报错
Traceback (most recent call last):
  File "/usr/lib/python3.6/code.py", line 91, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 2 were given

func(1,2,a=3,b=4)
Traceback (most recent call last):
  File "/usr/lib/python3.6/code.py", line 91, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
TypeError: func() takes 0 positional arguments but 2 positional arguments (and 2 keyword-only arguments) were given