在Python中的代码中经常会见到 *args 和 **kwargs。args 是 arguments 的缩写,*args表示位置参数;kwargs 是 keyword arguments 的缩写,**kwargs表示关键字参数。
args 与 kwargs 只是变量名,可以自定义其他变量名,重点在于单星号(*)和双星号(**)的解包操作符。
1. 什么是解包
- 解包(Unpacking)就是把一个容器拆开、分解,在Python中的解包是自动完成的。
- 解包操作可以应用于任何可迭代对象,如元组、字典、列表、集合、字符串、生成器等。
- 在Python3中,解包操作符有 * 和 **,一般情况下:
*
:用于列表、元组、集合**
:用于字典
2. 解包的使用
2.1 无星号解包操作
在Python2中的解包操作是无星号的自动解包,即没有解包操作符,要求 “变量数量 = 元素数量” 。
2.1.1 可迭代对象的解包
① 列表解包
>>> a, b, c = [1,2,3]
>>> a
1
>>> b
2
>>> c
3
② 元组解包
>>> a,b,c = (1,2,3)
>>> a
1
>>> b
2
>>> c
3
>>> a,b,c = enumerate(['a', 'b', 'c'])
>>> a
(0, 'a')
③ 字符串解包
>>> a,b,c = "abc"
>>> a
'a'
>>> b
'b'
>>> c
'c'
④ 字典解包
注意:解包后,只会把字典的 key 取出来,value 丢掉了。
>>> a,b,c = {"a":1, "b":2, "c":3}
>>> a
'a'
>>> b
'b'
>>> c
'c'
⑤ 生成器解包
>>> a,b,c = (x + 1 for x in range(3))
>>> a
1
>>> b
2
>>> c
3
2.1.2 多变量赋值
① 对多个变量同时赋值
>>> a, b = 1, 2
>>> a
1
>>> b
2
上述本质是自动解包的过程,等号右边其实是一个元组对象 (1, 2)
注意:对单个变量赋值数值时,若等号右边的代码不小心多了一个逗号 “,” 就变成了赋值元组对象,因此写代码的时候需要特别注意!
>>> a = 1, >>> a (1,)
② 交换两个变量
>>> a, b = 1, 2
>>> a, b = b, a
>>> a
2
>>> b
1
③ 接收函数返回的多个值
def test():
return 1, 2, 3
a, b, c = test()
print(a, b, c) # 1 2 3
函数的返回值是一个元组,左侧是三个变量,这样就会发生解包,a, b, c 依次等于元组里的元素,函数的返回值有3个,被封包成了元组。
Python3中,开始支持更高级的解包操作——星号解包操作( * 和 **),实现了 “变量数量 < 元素数量” 的解包。这个特性可以在 PEP 3132 中看到。
2.2 * 解包操作
2.2.1 可迭代对象的解包
- 收集元素
“变量数量 < 元素数量” 情况下,在 任意一个变量面前加一个星号,分配元素时,每个不带星号的变量优先分配一个元素后,剩下的元素都分配给带星号变量。星号变量收集的元素将放入一个列表list内。
>>> a, b, *c = (1,2,3,4)
>>> a
1
>>> b
2
>>> c
[3, 4]
相较于下例切片操作赋值,* 解包使得代码更简洁。
>>> n = [1,2,3,4]
# 使用切片操作
>>> a, b, c = n[0], n[1:-1], n[-1]
>>> a
1
>>> b
[2, 3]
>>> c
4
- 取出元素
>>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4}
{0, 1, 2, 3, 4}
注:单星号 * 对字典解包时,只能读取到字典中的键(key)。如下所示:
d = {'x': 1, 'y': 2} print(*d) # 输出 x y
- 拼接元素(取出元素的应用)
>>> list1 = [1,2,3]
>>> list2 = range(3,6)
>>> [*list1, *list3]
[1, 2, 3, 3, 4, 5]
*
相较于 +
虽然列表之间可以用 +
进行拼接,但是 list 类型无法与 range 对象直接通过+
拼接,必须先将 list2 强制转换为 list 对象才能做 +
拼接。*
使得我们的代码更加优雅!
2.2.2 函数调用时的解包操作
函数调用时,一个星号可作用于所有的可迭代对象,称为迭代器解包操作,作为位置参数传递给函数。
- 一个单星号的使用
定义如下 func 函数,函数中有三个位置参数 a,b,c,调用该函数必须传入三个参数。
def func(a,b,c):
print(a,b,c)
>>> func(1,2,3)
1 2 3
也可运用 * 解包含有3个元素的可迭代对象,作为参数传递
>>> func(*[1,2,3]) # 可迭代对象:列表
1 2 3
>>> func(*(1,2,3)) # 可迭代对象:元组
1 2 3
>>> func(*"abc") # 可迭代对象:字符串
a b c
>>> func(*{"a":1,"b":2,"c":3}) # 可迭代对象:字典
a b c
- 多个单星号的使用
在Python 3.5 之前的版本,函数调用时一个函数中解包操作只允许一个 * 和 一个 **,而从 3.5 开始,PEP 448 对解包操作做了进一步的扩展,在函数调用中,可以有任意多个解包操作。
如Python 3.4 中,print 函数 不允许多个 * 操作
>>> print(*[1,2,3], *[3,4])
File "<stdin>", line 1
print(*[1,2,3], *[3,4])
^
SyntaxError: invalid syntax
Python 3.5 以上版本以使用任意多个解包操作
>>> print(*[1], *[2], 3)
1 2 3
2.3 ** 解包操作
2.3.1 可迭代对象的解包
- 取出元素
>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}
注意:在 解包多个字典放入新的字典 时,对相同的键,后者对应的值会覆盖(override)前者对应的值。
>>> {'x': 1, **{'x': 2}} {'x': 2} >>> {**{'x': 2}, 'x': 1} {'x': 1}
注意:调用函数过程中,通过解包多个字典传参时,解包后不允许存在相同关键字,如
f(**{‘x’: 2}, **{‘x’: 3})
- 拼接元素(取出元素的应用)
>>> a = {"a":1, "b":2}
>>> b = {"c":3, "d":4}
>>> {**a, **b}
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
2.3.2 函数调用时的解包操作
函数调用时,两个星号的解包操作只能作用于字典对象,称之为字典解包操作。解包时会自动解包字典对象成关键字参数 key=value 的格式,传递给函数。
def func(a,b,c):
print(a,b,c)
>>> func(**{"a":1,"b":2,"c":3})
1 2 3
相较于以下方式,** 解包的好处是节省代码量,使代码看起来更优雅。
>>> d = {"a":1, "b":2, "c":3}
>>> func(a = d['a'], b=d['b'], c=d['c'])
1 2 3
注意:** 解包字典后是 key = value的键值格式。上例字典的 key 值正好 是 func 函数的形参值。但是 print 函数是不能直接打印 ** 解包字典的结果的,如下所示:
c = {"name": 'zhang', "age": 2} print(**c)
TypeError: 'name' is an invalid keyword argument for print()
由于 ** 解包出来的数据是这样的 :**c = name=‘zhang’,age=2。且 print 函数的源码如下:def print(self, *args, sep=' ', end='\n', file=None): # known special case of print 只支持 *args,不支持 **kwargs。因此尝试写成如下形式进行打印输出。>>> print("Name:{name}, Age:{age}".format(**c)) Name:zhang, Age:2
2.4 函数定义中 * 和 ** 的混合使用
- *args 与 **kwargs 是 Python 中 可变参数 的两种形式。
- 在函数定义中位置参数必须在关键字参数的前面(即*args,**kwargs的顺序)。
- 在函数定义中,形参的 *args 是接受位置参数的 args,args 是一个元组,把传入的数据放进其中;形参的 **kwargs 是接受关键字参数的 kwargs,kwargs 是一个字典,把传入关键字键值放进其中。
- 函数调用传入实参时,可变参数(*)之后的参数必须指定参数名传参,否则将被归到可变参数(*)之中。
如下所示,*args 收集位置参数后传递一个可变参数元组 给函数实参,**para 收集关键字参数后传递一个可变的关键字参数的字典 给函数实参(这些参数的数目未知,甚至长度可以为0)。
def test_kwargs(first, *args, **kwargs):
print('Required argument: ', first)
print(type(args))
print(type(kwargs))
for v in args:
print ('Optional argument (args): ', v)
for k, v in kwargs.items():
print ('Optional argument %s (kwargs): %s' % (k, v))
test_kwargs(1, 2, 3, 4, k1=5, k2=6)
正如前面所说的,args 变量的类型是一个 tuple,而 kwargs 变量则是一个字典dict,并且args只能位于kwargs的前面。
test_kwargs 函数中,第一个参数 first 是形参,是必须要传入的参数,不带关键字的参数则作为可变参数列表传入函数实参,带关键字的参数则作为可变参数字典传入函数实参。所以test_kwargs(1, 2, 3, 4, k1=5, k2=6)
中,1
作为实参传入了 first;紧接着 2, 3, 4
三个数则作为可变参数列表传入实参,最后的 k1=5, k2=6
中的5,6作为可变参数字典传入实参。
运行结果如下:
Required argument: 1
<class 'tuple'>
<class 'dict'>
Optional argument (args): 2
Optional argument (args): 3
Optional argument (args): 4
Optional argument k2 (kwargs): 5
Optional argument k1 (kwargs): 6
3. 解包的应用
3.1 * 的应用
3.1.1 列表转元组巧妙方法
stars = ["黎明", "华仔", "郭富城", "张学友"]
# 方法一
print("四大天王:%s, %s, %s, %s" % (stars[0], stars[1], stars[2], stars[3]))
# 方法二
print("四大天王:%s, %s, %s, %s" % tuple(stars))
# 方法三
print("四大天王:%s, %s, %s, %s" % (*stars,))
这三种方式输出结果一致,(*stars,)
解包列表并转换了一个元组。
3.1.2 实现类似矩阵转置的操作
a = [[1, 2, 3], [4, 5, 6]]
for x, y in zip(*a):
print(x, y)
# 1 4
# 2 5
# 3 6
3.1.2 切分嵌套可迭代对象数据
>>> l = [('Bob', '1990-1-1', 60),
... ('Mary', '1996-1-4', 50),
... ('Nancy', '1993-3-1', 55)]
>>> for name, *args in l:
... print(name, args)
...
Bob ['1990-1-1', 60]
Mary ['1996-1-4', 50]
Nancy ['1993-3-1', 55]
3.2 * 和 ** 的应用之装饰器
>>> def mydecorator(func):
... def wrapper(*args, **kw):
... print('I am using a decorator.')
... return func(*args, **kw)
... return wrapper
...
>>> @mydecorator
... def myfun(a, b):
... print(a)
... print(b)
...
>>> myfun(1, b = 2)
I am using a decorator.
1
2
使用@
定义myfun
相当于myfun = mydecorator(myfun)
,定义出来的myfun
其实是返回结果wrapper
函数。
wrapper
函数使用*args, **kw
作为参数,则被修饰的myfun
函数需要的参数无论是什么样的,传入wrapper
都不会报错,这保证了装饰器可以修饰各种各样函数的灵活性(毕竟我们一般在函数中传入参数时,要么所有参数名都写,要么前面几个不写,后面的会写,这样使用*args, **kw完全没有问题)。
4. 一些补充
4.1 函数传参时,可变参数(*)前不能指定参数名传参
def myfun(a, *b):
print(a)
print(b)
myfun(a=1,2,3,4)
报错如下:
myfun(a=1,2,3,4)
^
SyntaxError: positional argument follows keyword argument
正确的调用方法为:
>>> myfun(1,2,3,4)
1
(2, 3, 4)
4.2 设计调用函数时必须使用参数名传参
一个函数想要使用时必须明确指定参数名进行传参,可以将这些参数放在可变参数之后。
def myfun(*, a, b):
print(a)
print(b)
myfun(a=1, b=2)
4.3 函数定义时,关键字参数只能作为最后一个参数
def myfun(a, *b, c, **d):
print(a)
print(b)
print(c)
print(d)
myfun(1, 2, w=6, c=3, d=4, e=5) # 记住可变参数(*)之前不能指定参数名
运行结果如下:
1
(2,)
3
{'w': 6, 'd': 4, 'e': 5}
4.4 _ 与 * 赋值的区别
- 一些元素不用时,采取分配临时名称“_”存着,可以让读代码的人知道这个元素是不要的,这种写法要求赋值时等号两侧的元素个数必须相等,即“_”相当于一个匿名变量,与普通变量一样只存储一个元素。
- * 解包使得“变量数量 < 元素数量” 的赋值情况得以实现。
观察下例,“_” 与 “ * ” 的混合使用:
例①
>>> person = ('Bob', 20, 50, (11, 20, 2000))
>>> name, *_, (*_, year) = person
>>> name
'Bob'
>>> year
2000
例②
>>> l = [1,2,3,4,5,6,7,8,9,10,11,12]
>>> a,b,*_,c = l
>>> print(a,b,_,c)
1 2 [3, 4, 5, 6, 7, 8, 9, 10, 11] 12
参考
Python中的*args和**kwargs
Python中星号(*)的使用
Python的解包
python基础语法之拆包(解包)
python中的装包与解包*,**
python有趣的解包用法