*运算符在python中的解压、传参用法
这两天得了一本《Python Cookbook》,甚是喜爱,希望能记录一点有意思的Python 用法,既是作为自己的读书笔记,让自己能够不断精进技术,也是为了能够和大家一起讨论Python。
从zip说开去
*操作符在Python中除了乘法和复制(如[1] * 5)的用法之外,还有解压(unpack)的功能,这应该是所有用过zip()的人都知道的事情。zip函数的基本用法如下:
stuff = ['apple','banana','peach']
money = [10, 5, 7]
pair = list(zip(stuff,money))
# pair = [('apple',10),('banana',5),('peach',7)]
但是如果我们现在已经有 pair 这个 list 了,希望能够还原成stuff 和 money 两个 list,我们就需要用到*符号:
stuff,money = zip(*pair)
在这里*就起到了解压的效果
*对于迭代对象的作用
当然了,*并不是说只能与 zip() 联合使用,事实上,任何一个函数都可以用到*符号,只要它操作的对象是可迭代的:
def do_something(x,y,z):
print('x is ', x)
print('y is ', y)
print('z is ', z)
list1 = ['a','b','c']
>>> do_something(list1)
Trackback (most recent call last):
File "<stdin>", line1, in <module>
TypeError: do() missing 2 required positional arguments: 'y' and 'z'
>>> do_something(*list1)
x is a
y is b
z is c
可知,*
操作符自动将 list1 中的元素赋给了三个形参,这就是*
很方便的用法
**的用法
当然,对于字典(dict)来说,我们也可以使用*
运算符:
dict1 = {'x':1, 'y':2, 'z':3}
>>> do_something(*dict1)
x is x
y is y
z is z
但是这样子得到的是 key 值,如果想要得到 value 值,我们需要使用**
运算符:
>>> do_somthing(**dict1)
x is 1
y is 2
z is 3
一定要注意的是,此处的用法必须要求函数形参与字典 key 值一一对应,请看下面的用法:
dict2 = {'z':1, 'x':2, 'y':3}
>>> do_something(**dict2)
x is 2
y is 3
z is 1
dict3 = {'a':1, 'b':2, 'c':3}
>>> do_something(**dict3)
# TypeError: do_something() missing 1 required positional argument: 'x'
实际上,**
的用法应该这么理解:
**dict2 等效于 z=1, x=2, y=3, 因此 do_something(**dict2) 等效于 do_something(z=1, x=2, y=3), 这样看来,dict3的错误就是必然的了,因为函数没有 a 这个参数
为了加深理解,请看下面这条例子(来自Reference 2):
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
>>> filename
'2020-01-01.txt'
理解 *args
和**kwargs
*args
和**kwargs
是我们经常见到的两个参数,但这两个参数究竟是干什么的可能有些人仍有疑惑。其实它们的用法也很简单,对于一个函数而言,如果你不确定它还有什么待传入的参数,就不妨用一个*args
(当然你不一定非得叫args
,但一般都喜欢起这个名字),它将输入的多余形参以元组形式保存在args
中:
#两个数的加法
def adder_2(x,y):
return x+y
#三个数的加法
def adder_3(x,y,z):
return x+y+z
# 无限个数的加法
def adder_unlimited(*args):
result = 0
for num in args:
result += num
return result
>>> adder_unlimited(1)
1
>>> adder_unlimited(1,2)
3
>>> adder_unlimited(1,2,3,4)
10
>>> list_num = [1,2,3,4]
>>> adder_unlimited(*list_num) #活学活用
10
**kwargs
效果则是将输入的未定义的形参及其对应的值存在kwargs
字典里(例子来源Reference 4):
def intro(**data):
print("nData type of argument:",type(data))
for key, value in data.items():
print("{} is {}".format(key,value))
>>> intro(Firstname="Sita", Lastname="Sharma", Age=22, Phone=1234567890)
Data type of argument: <class 'dict'>
Firstname is Sita
Lastname is Sharma
Age is 22
Phone is 1234567890
>>> intro(Firstname="John", Lastname="Wood", Email="johnwood@nomail.com", Country="Wakanda", Age=25, Phone=9876543210)
Data type of argument: <class 'dict'>
Firstname is John
Lastname is Wood
Email is johnwood@nomail.com
Country is Wakanda
Age is 25
Phone is 9876543210
因此,对于一个好的 API 来说,应该尽量使用*args
和**kwargs
以提高程序稳定性
利用*从可迭代对象中分解元素
说了这么多终于说到了《Python Cookbook》中的内容了(内心OS:累死我了)。 假设我们现在要对一个歌手的表现打分,去掉一个最高分,去掉一个最低分,对其他所有分数(未知个数)求平均分,这时*
方法就派上用场了:
def drop_first_last(grades):
first, *middle, last = grades
return avg(middle)
也许你会说,用切片也可以达到同样效果,如middle = grades[1:-1]
,但如果我们需要用到first
和last
呢?你是不是还要first = grades[0]
和last = grades[-1]
呢? 太不优雅了! 事实上,下面这个例子如果用切片来写就十分麻烦(来源 Reference 3):
>>>line = 'nobody:*:-2:-2:Unprevileged User:/var/empty:/usr/bin/false'
>>> uname,*_, homedir, sh = line.split(':')
>>> uname
'nobody'
>>> homedir
'/var/empty'
>>> sh
'/usr/bin/false'
其中*_
将我们 split 之后不想要的部分直接丢弃了(一般用下划线来为我们不需要的变量取名)
实际上,*
操作和函数式语言中的列表处理功能相似,相信学过Coursera 华盛顿大学神课Programming Language的同学对这一形式一定不会陌生(来源 Reference3):
def sum(items):
head, *tail = items
return head + sum(tail) if tail else head
当然,限于Python自身内部对递归的次数限制,这个例子在实践中意义不大,但是它确实是十分精妙,令人不禁击节赞叹!
其他奇技淫巧
还有一些我不知道怎么分类的小技术,在这里也一并献给大家(例子来自 Reference 2):
列表元素快速换位置
sequence = [*sequence[1:], sequence[0]] #将首个元素换到最后一个
将多个迭代对象转化为 list
很多时候,我们在使用完一个函数时,他返回的值都是generator 等迭代对象,必须套上一个list()
才可以变为列表,如zip()
,reversed()
等,此时如果每个函数都套上list()
则显得不够优雅,更好的方式如下:
def palindromify(sequence):
return list(sequence) + list(reversed(sequence)) #不够优雅
def palindromify(sequence):
return [*sequence, *reversed(sequence)] # better
同理,多个 list 的拼接也可以如法炮制:
list_all = [*list1,*list2,*list3,*list4]
字典的合并
同理,字典的合并用**
也是十分方便:
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> all_info = {**date_info, **track_info}
>>> all_info {'year': '2020', 'month': '01', 'day': '01', 'artist': 'Beethoven', 'title': 'Symphony No 5'}
最后的闲言碎语
第一次写技术博客,竟然写了三个小时,大概算是涵盖了大部分*
的用法,希望这是一个好的开始,也希望所有读者能多提些宝贵意见,谢谢! 之后我还想补充一下Python中*
是如何实现的,不过这个可能得去翻源码才能有所斩获吧!
Reference
- Python Zip()
- Asterisks in Python: what they are and how to use them
- Python Cookbook
- Python *args and **kwargs