Python中的args和kwargs
2019-09-08 19:11 abce 阅读(11791) 评论(1) 编辑 收藏 举报有时,你会看到python中定义函数的时候带有两个奇怪的参数:*args、**kwargs。如果你曾经想知道它们是干什么的,或者想知道你的IDE为什么在main()函数中定义它们,那么本文可以帮助到你。本文会告诉你在python中如何使用args和kwargs,来增加函数的灵活性。
1.传递多个参数给函数
*args和*kwargs允许你给一个参数传递多个参数或者keyword参数。考虑下面的例子。这是一个简单的函数,需要获取两个参数并返回它们之和:
1 2 | def my_sum(a, b): return a + b |
这个函数可以正常工作,但它仅限于两个参数。如果需要对不同数量的参数求和,如果传递的特定参数数量仅在运行时确定,该怎么办?创建一个可以对传递给它的所有整数求和的函数,不管是多少个参数,是不是很好?
2.在python函数定义中使用变量args
有多种方法可以给一个函数传递不同数量的参数。
对于有经验的人来说,第一种最直观的方法是使用集合。简单地传递一个list或者set作为函数的参数。因此,对于my_sum(),你可以将你所有要相加的所有整数以一个list的形式传入:
1 2 3 4 5 6 7 8 9 | # sum_integers_list.py def my_sum(my_integers): result = 0 for x in my_integers: result + = x return result list_of_integers = [ 1 , 2 , 3 ] print (my_sum(list_of_integers)) |
可以这样实现,但是每当你要调用这个函数的时候,你就需要创建一个list作为参数传入。这样可能并不方便,尤其是你实现并不知道要加入list的所有值的时候。
这就是*args的作用之处了,它可以让你传递可变数量的位置参数。以下为示例:
1 2 3 4 5 6 7 8 9 | # sum_integers_args.py def my_sum( * args): result = 0 # Iterating over the Python args tuple for x in args: result + = x return result print (my_sum( 1 , 2 , 3 )) |
这个例子中,你不再需要向my_sum()函数传递一个list。而是传递三个不同的位置参数。my_sum()会获取所有输入的参数,并将它们打包成一个可迭代的简单对象,命名为args。
注意,args只是一个名字。你可以不用args这个名字。你可以选择任何你喜欢的名字,比如integers:
1 2 3 4 5 6 7 8 | # sum_integers_args_2.py def my_sum( * integers): result = 0 for x in integers: result + = x return result print (my_sum( 1 , 2 , 3 )) |
这个函数仍然正常工作,即使你传递的可迭代对象是integers而不是args。这里最重要的是你使用的解包(unpacking)操作符(*)。
请记住,你使用解包(unpacking)操作符*获得的可迭代对象不是一个list,而是一个元组(tuple)。
一个元组(tuple)类似一个list,它们都支持切片和迭代。然而,元组(tuple)又是和list不同的,至少在一个方面:lists是可变的、tuple是不可变的。
为了测试这点,可以运行以下的代码。这个脚本尝试去修改一个list的值:
1 2 3 4 | # change_list.py my_list = [ 1 , 2 , 3 ] my_list[ 0 ] = 9 print (my_list) |
list中,第一个元素的值就被更新成了9。如果你执行这个脚本,你会看到list的值的确被修改了
1 2 | $ python change_list.py [ 9 , 2 , 3 ] |
第一个元素的值不再是0,而是被更新成了9。现在,尝试对一个元组(tuple)做相同的操作:
1 2 3 4 | # change_tuple.py my_tuple = ( 1 , 2 , 3 ) my_tuple[ 0 ] = 9 print (my_tuple) |
这里,你可以看到相同的值,除了它们被作为一个元组被放在一起。如果你尝试执行脚本,你会看到python解释器返回了一个error:
1 2 3 4 5 | $ python change_tuple.py Traceback (most recent call last): File "change_tuple.py" , line 3 , in <module> my_tuple[ 0 ] = 9 TypeError: 'tuple' object does not support item assignment |
这是因为元组(tuple)是不可变对象,它的值不能在指定后就不能被更改。请牢记这一点,当你使用tuple和*args的时候。
3.在python函数定义中使用变量kwargs
到这里,你已经知道*args的用途了,但是**kwargs呢?**kwargs工作原理和*args有点类似,但不是接收位置参数,而是接收关键字(keyword)参数(也叫被命名的参数)。以下为例:
1 2 3 4 5 6 7 8 9 | # concatenate.py def concatenate( * * kwargs): result = "" # Iterating over the Python kwargs dictionary for arg in kwargs.values(): result + = arg return result print (concatenate(a = "Real" , b = "Python" , c = "Is" , d = "Great" , e = "!" )) |
执行上面的脚本,concatenate()会通过python的kwargs字典进行迭代并将找到的所有值连接起来:
1 2 | $ python concatenate.py RealPythonIsGreat! |
和args类似,kwargs只是一个名字,可以修改成任何你想要的名字。最重要的是解包(unpacking operator)操作符(**)的用途。
因此,上面的例子可以写成这样:
1 2 3 4 5 6 7 8 | # concatenate_2.py def concatenate( * * words): result = "" for arg in words.values(): result + = arg return result print (concatenate(a = "Real" , b = "Python" , c = "Is" , d = "Great" , e = "!" )) |
在上面例子中,可迭代对象是标准的字典(dict)。如果你迭代字典并想返回值,你就必须使用.values(),就像例子中那样所示。
事实上,如果你忘记了这个方法,你会发现你的迭代是通过你的python的kwargs字典的键实现的,就下下面的例子所示:
1 2 3 4 5 6 7 8 9 | # concatenate_keys.py def concatenate( * * kwargs): result = "" # Iterating over the keys of the Python kwargs dictionary for arg in kwargs: result + = arg return result print (concatenate(a = "Real" , b = "Python" , c = "Is" , d = "Great" , e = "!" )) |
现在,你再执行示例,你会发现以下结果输出:
1 2 | $ python concatenate_keys.py abcde |
可以看到,如果你不指定.values(),你的函数会通过键进行迭代你的python的kwargs字典,返回错误的结果。
4.函数中参数的顺序
既然你已经学习了*args和**kwargs是干什么的,你可以开始编写获取不同数量的参数的函数了。但是,如果你想创建一个函数,该函数接受可变数量的位置参数和命名参数,该怎么办?
这时,你就需要记住顺序很重要。非默认参数必须在默认参数之前处理,因此*args在**kwargs的前面。
总结一下,参数的正确顺序是:
(1)位置参数
(2)*args参数
(3)**kwargs参数
例如,以下函数的定义是正确的:
1 2 3 | # correct_function_definition.py def my_function(a, b, * args, * * kwargs): pass |
*args变量在**kwargs的前面。但是,如果你想修改参数的顺序呢?例如,考虑下面的函数:
现在,函数定义中**kwargs在*args的前面。如果你想运行这个例子,你会接收到来自解释器一个错误:
1 2 3 4 5 | $ python wrong_function_definition.py File "wrong_function_definition.py" , line 2 def my_function(a, b, * * kwargs, * args): ^ SyntaxError: invalid syntax |
这种情况下,因为*args在**kwargs的后面,python解释器抛出SyntaxError。
这里还可以分场景继续细化出其它场景:
(1)如果只有位置参数、默认参数、*args。顺序是:(位置参数,默认参数,*args)或者(位置参数,*args,默认参数)
1 2 3 4 5 6 7 8 9 | (位置参数,默认参数, * args) def foo(x,y = 1 , * args): pass foo ( 1 , 2 , 3 , 4 , 5 ) / / 其中的x为 1 ,y = 1 的值被 2 替换, 3 , 4 , 5 都给args,即args = ( 3 , 4 , 5 ) (位置参数, * args,默认参数) def foo(x, * args,y = 1 ): pass foo ( 1 , 2 , 3 , 4 , 5 ) / / 其中的x为 1 , 2 , 3 , 4 , 5 都给args,即args = ( 2 , 3 , 4 , 5 ),y始终为 1 |
(2)位置参数、默认参数、*args*和*kwargs同时出现。顺序是:(位置参数,*args*,默认参数,*kwargs)
1 2 3 4 5 6 7 | def foo1(x, y, * args, a = 8 , b = 9 , * * kwargs): pass foo1( 1 , 2 , 3 , 4 ,a = 5 ,b = 6 ,y = 7 ) #其中的x为1,y为2 #3,4都给args,即args=(3,4) #a,b分别被替换成5,6 #y=7以字典形式传给kwargs |
如果不带默认参数:
1 2 3 4 5 6 | def foo2(x, y, * args, * * kwargs): pass foo2( 1 , 2 , 3 , 4 ,a = 5 ,b = 6 ,y = 7 ) #其中的x为1,y为2 #3,4都给args,即args=(3,4) #a=5,b=6,y=7以字典形式传给kwargs |
5.解包(unpacking)星号操作符:*和**
现在你可以使用*args和**kwargs来定义获取变化的输入参数的python函数了。让我们再深入地理解解包(unpacking)操作符。
单个星号(*)和两个星号(**)解包操作符是在Python2中引入的。在3.5版本中,它们变得更强大。简而言之,解包(unpacking)操作符是将python中可迭代对象的值解包的操作符。单个星号操作符可以用在任意python提供的可迭代对象上,两个星号操作符只能用于字典。
我们从下面这个例子开始:
1 2 3 | # print_list.py my_list = [ 1 , 2 , 3 ] print (my_list) |
代码定义了一个list,然后将其打印输出到标准输出:
1 2 | $ python print_list.py [ 1 , 2 , 3 ] |
注意列表是如何打印的,以及相应的括号和逗号。
现在,试着把解包操作符*添加到列表中:
1 2 3 | # print_unpacked_list.py my_list = [ 1 , 2 , 3 ] print ( * my_list) |
这里,*操作符告诉print()首先将list解包。
在这个例子中,输出不再是list本身,而是list的内容:
1 2 | $ python print_unpacked_list.py 1 2 3 |
你能看出这个例子执行结果和print_list.py有什么不同么?print()已经将三个不同的参数作为输入,而不是以一个list作为输入。
另外你可能看到,在print_unpacked_list.py中,你使用了解包操作符(*)来调用函数,而不是用在函数定义中。在这里,print()将list中的单个item作为一个个参数。
你也可以使用这个方法调用自己的函数,但是如果你的函数需要特定数量的参数,那么你解包的iterable必须具有相同数量的参数。
为了测试这个行为,考虑以下的脚本:
1 2 3 4 5 6 | # unpacking_call.py def my_sum(a, b, c): print (a + b + c) my_list = [ 1 , 2 , 3 ] my_sum( * my_list) |
这里,my_sum()显式声明a,b,c是需要的参数。
如果你运行这个脚本,你会获得my_list中三个数的和:
1 2 | $ python unpacking_call.py 6 |
my_list中的三个元素完美地匹配了my_sum()需要的参数。
现在,看一下下面的脚本,my_list有四个参数而不是三个:
1 2 3 4 5 6 | # wrong_unpacking_call.py def my_sum(a, b, c): print (a + b + c) my_list = [ 1 , 2 , 3 , 4 ] my_sum( * my_list) |
在这个例子中,my_sum()仍然期待三个参数,但是*操作符从list中获得了四个。如果你尝试执行这个脚本,你会发现python解释器仍然可以运行它:
1 2 3 4 5 | $ python wrong_unpacking_call.py Traceback (most recent call last): File "wrong_unpacking_call.py" , line 6 , in <module> my_sum( * my_list) TypeError: my_sum() takes 3 positional arguments but 4 were given |
当你用*操作符去解包一个list并传递给函数作为参数,就好像你在传递每一个单独的参数。
这表示你可以使用多个解包(unpacking)操作符,从多个lists中获取值并作为参数传递个一个函数。
可以用以下的示例来测试:
1 2 3 4 5 6 7 8 9 10 11 12 | # sum_integers_args_3.py def my_sum( * args): result = 0 for x in args: result + = x return result list1 = [ 1 , 2 , 3 ] list2 = [ 4 , 5 ] list3 = [ 6 , 7 , 8 , 9 ] print (my_sum( * list1, * list2, * list3)) |
如果你运行这个例子,所有的lists都被解包。每个单独的项被传递给my_sum(),结果如下:
1 2 | $ python sum_integers_args_3.py 45 |
解包操作符还有其他方便的用途。例如,假设你需要将列表分成三个不同的部分。输出应该显示第一个值、最后一个值和中间的所有值。使用解包操作符,你可以用一行代码完成:
1 2 3 4 5 6 7 8 | # extract_list_body.py my_list = [ 1 , 2 , 3 , 4 , 5 , 6 ] a, * b, c = my_list print (a) print (b) print (c) |
在这个例子中,my_list包含6个项。第一个变量被分配给a,最后一个被分配给c,其它的值都被打包成一个list b。如果你运行一下,print()会显示三个变量的值:
1 2 3 4 | $ python extract_list_body.py 1 [ 2 , 3 , 4 , 5 ] 6 |
另一个有趣的事是,你可以使用解包操作符(*)来对任何可迭代对象进行分片。如果你需要将两个list进行合并,就会非常有用:
1 2 3 4 5 6 | # merging_lists.py my_first_list = [ 1 , 2 , 3 ] my_second_list = [ 4 , 5 , 6 ] my_merged_list = [ * my_first_list, * my_second_list] print (my_merged_list) |
解包操作符(*)作为my_first和my_second的前缀。
如果你运行脚本,你会看到一个合并的list:
1 2 | $ python merging_lists.py [ 1 , 2 , 3 , 4 , 5 , 6 ] |
你可以合并两个不同的字典,通过解包操作符(**):
1 2 3 4 5 6 | # merging_dicts.py my_first_dict = { "A" : 1 , "B" : 2 } my_second_dict = { "C" : 3 , "D" : 4 } my_merged_dict = { * * my_first_dict, * * my_second_dict} print (my_merged_dict) |
这里,迭代合并了my_first_dict和my_second_dict。
执行这个代码,输出一个合并后的字典:
1 2 | $ python merging_dicts.py { 'A' : 1 , 'B' : 2 , 'C' : 3 , 'D' : 4 } |
请牢记,*操作可以对任意可迭代对象起作用。可以对一个字符串进行解包操作:
1 2 3 | # string_to_list.py a = [ * "RealPython" ] print (a) |
在python中,字符串是可迭代对象,因此*会解包字符串并将单个值放入list a中:
1 2 | $ python string_to_list.py [ 'R' , 'e' , 'a' , 'l' , 'P' , 'y' , 't' , 'h' , 'o' , 'n' ] |
在使用这些操作符的时候,要记住代码的可读性很重要。
考虑以下的代码:
1 2 3 | # mysterious_statement.py * a, = "RealPython" print (a) |
这里的解包操作符*,后面跟了一个变量,一个逗号和一个赋值。一行中打包了很多东西,这个代码和上面的代码没有什么区别。只是将字符串RealPyhton中所有的项指定到一个新的list a。
a后面的逗号就可以了。当使用带有变量赋值的解包操作符时,Python要求得到的变量要么是列表,要么是元组。使用后面的逗号,实际上已经定义了一个只有一个命名变量a的元组。
虽然这是一个巧妙的技巧,但许多Pythonistas并不认为这段代码可读性很强。因此,最好少用这类结构。
6.结论
现在,在你的python函数中,可以使用*args和**kwargs来接收可变数量的参数了。你也了解了解包操作符。
你已经学会:
(1)*args和**kwargs的含义。*args:非关键字参数、**kwargs:关键字参数
(2)如何使用*args和**kwargs来定义函数
(3)如何使用单个星号(*)来解包可迭代对象
(4)如何使用两个星号(**)来解包字典对象
7.python小技巧
1 2 3 4 5 6 7 | # How to merge two dicts # in Python 3.5+ >>> x = { 'a' : 1 , 'b' : 2 } >>> y = { 'b' : 3 , 'c' : 4 } >>> z = { * * x, * * y} >>> z { 'c' : 4 , 'a' : 1 , 'b' : 3 } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)