有時,你會看到python中定義函數的時候帶有兩個奇怪的參數:*args、**kwargs。如果你曾經想知道它們是干什么的,或者想知道你的IDE為什么在main()函數中定義它們,那么本文可以幫助到你。本文會告訴你在python中如何使用args和kwargs,來增加函數的靈活性。
1.傳遞多個參數給函數
*args和*kwargs允許你給一個參數傳遞多個參數或者keyword參數。考慮下面的例子。這是一個簡單的函數,需要獲取兩個參數並返回它們之和:
def my_sum(a, b): return a + b
這個函數可以正常工作,但它僅限於兩個參數。如果需要對不同數量的參數求和,如果傳遞的特定參數數量僅在運行時確定,該怎么辦?創建一個可以對傳遞給它的所有整數求和的函數,不管是多少個參數,是不是很好?
2.在python函數定義中使用變量args
有多種方法可以給一個函數傳遞不同數量的參數。
對於有經驗的人來說,第一種最直觀的方法是使用集合。簡單地傳遞一個list或者set作為函數的參數。因此,對於my_sum(),你可以將你所有要相加的所有整數以一個list的形式傳入:
# 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的作用之處了,它可以讓你傳遞可變數量的位置參數。以下為示例:
# 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:
# 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的值:
# change_list.py my_list = [1, 2, 3] my_list[0] = 9 print(my_list)
list中,第一個元素的值就被更新成了9。如果你執行這個腳本,你會看到list的值的確被修改了
$ python change_list.py [9, 2, 3]
第一個元素的值不再是0,而是被更新成了9。現在,嘗試對一個元組(tuple)做相同的操作:
# change_tuple.py my_tuple = (1, 2, 3) my_tuple[0] = 9 print(my_tuple)
這里,你可以看到相同的值,除了它們被作為一個元組被放在一起。如果你嘗試執行腳本,你會看到python解釋器返回了一個error:
$ 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)參數(也叫被命名的參數)。以下為例:
# 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字典進行迭代並將找到的所有值連接起來:
$ python concatenate.py RealPythonIsGreat!
和args類似,kwargs只是一個名字,可以修改成任何你想要的名字。最重要的是解包(unpacking operator)操作符(**)的用途。
因此,上面的例子可以寫成這樣:
# 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字典的鍵實現的,就下下面的例子所示:
# 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="!"))
現在,你再執行示例,你會發現以下結果輸出:
$ python concatenate_keys.py abcde
可以看到,如果你不指定.values(),你的函數會通過鍵進行迭代你的python的kwargs字典,返回錯誤的結果。
4.函數中參數的順序
既然你已經學習了*args和**kwargs是干什么的,你可以開始編寫獲取不同數量的參數的函數了。但是,如果你想創建一個函數,該函數接受可變數量的位置參數和命名參數,該怎么辦?
這時,你就需要記住順序很重要。非默認參數必須在默認參數之前處理,因此*args在**kwargs的前面。
總結一下,參數的正確順序是:
(1)位置參數
(2)*args參數
(3)**kwargs參數
例如,以下函數的定義是正確的:
# correct_function_definition.py def my_function(a, b, *args, **kwargs): pass
*args變量在**kwargs的前面。但是,如果你想修改參數的順序呢?例如,考慮下面的函數:
現在,函數定義中**kwargs在*args的前面。如果你想運行這個例子,你會接收到來自解釋器一個錯誤:
$ 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,默認參數)
(位置參數,默認參數,*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)
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
如果不帶默認參數:
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提供的可迭代對象上,兩個星號操作符只能用於字典。
我們從下面這個例子開始:
# print_list.py my_list = [1, 2, 3] print(my_list)
代碼定義了一個list,然后將其打印輸出到標准輸出:
$ python print_list.py [1, 2, 3]
注意列表是如何打印的,以及相應的括號和逗號。
現在,試着把解包操作符*添加到列表中:
# print_unpacked_list.py my_list = [1, 2, 3] print(*my_list)
這里,*操作符告訴print()首先將list解包。
在這個例子中,輸出不再是list本身,而是list的內容:
$ python print_unpacked_list.py 1 2 3
你能看出這個例子執行結果和print_list.py有什么不同么?print()已經將三個不同的參數作為輸入,而不是以一個list作為輸入。
另外你可能看到,在print_unpacked_list.py中,你使用了解包操作符(*)來調用函數,而不是用在函數定義中。在這里,print()將list中的單個item作為一個個參數。
你也可以使用這個方法調用自己的函數,但是如果你的函數需要特定數量的參數,那么你解包的iterable必須具有相同數量的參數。
為了測試這個行為,考慮以下的腳本:
# 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中三個數的和:
$ python unpacking_call.py 6
my_list中的三個元素完美地匹配了my_sum()需要的參數。
現在,看一下下面的腳本,my_list有四個參數而不是三個:
# 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解釋器仍然可以運行它:
$ 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中獲取值並作為參數傳遞個一個函數。
可以用以下的示例來測試:
# 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(),結果如下:
$ python sum_integers_args_3.py 45
解包操作符還有其他方便的用途。例如,假設你需要將列表分成三個不同的部分。輸出應該顯示第一個值、最后一個值和中間的所有值。使用解包操作符,你可以用一行代碼完成:
# 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()會顯示三個變量的值:
$ python extract_list_body.py 1 [2, 3, 4, 5] 6
另一個有趣的事是,你可以使用解包操作符(*)來對任何可迭代對象進行分片。如果你需要將兩個list進行合並,就會非常有用:
# 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:
$ python merging_lists.py [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。
執行這個代碼,輸出一個合並后的字典:
$ python merging_dicts.py {'A': 1, 'B': 2, 'C': 3, 'D': 4}
請牢記,*操作可以對任意可迭代對象起作用。可以對一個字符串進行解包操作:
# string_to_list.py a = [*"RealPython"] print(a)
在python中,字符串是可迭代對象,因此*會解包字符串並將單個值放入list a中:
$ python string_to_list.py ['R', 'e', 'a', 'l', 'P', 'y', 't', 'h', 'o', 'n']
在使用這些操作符的時候,要記住代碼的可讀性很重要。
考慮以下的代碼:
# 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小技巧
# 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}