Python中的args和kwargs


有時,你會看到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}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM