Python的解包知识

  在Python中的代码中经常会见到 *args**kwargsargs 是 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有趣的解包用法

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值