Python中*args 和**kwargs的用法


剛開始學習python的時候,對有關args,kwargs,和*的使用感到很困惑。相信對此感到疑惑的人也有很多。我打算通過這個帖子來排解這個疑惑(希望能減少疑惑)。

讓我們通過以下5步來理解: 
1. 通過一個函數調用來理解’*’的作用 
2. 通過一個函數的定義來理解’*args’的含義 
3. 通過一個函數的調用來理解’**’的作用 
4. 通過一個函數的定義來解’**kwargs’的含義 
5. 通過一個應用實例來說明’args’,’kwargs’應用場景以及為何要使用它


通過一個函數調用來理解’*’的作用

定義一個含三個位置參數的函數”fun”.

>>> def fun(a,b,c): ... print a,b,c ... 

傳三個位置參數調用此函數

>>> fun(1,2,3) 1 2 3 #輸出

可以看到出入三個位置參數調用此函數,會打印出三個參數

現在我們定義一個含三個整數的數列,並使用’*’

>>> l = [1,2,3] >>> fun(*l) 1 2 3 #輸出

‘*’ 做了什么?

它拆開數列’l’的數值作為位置參數,並吧這些位置參數傳給函數’fun’來調用。

因此拆數列、傳位置參數意味着fun(*l)與fun(1,2,3)是等效的,因為l = [1,2,3]。

試着數列中使用其他數值

>>> l = [4,8,0] >>> fun(*l) 4 8 0 #輸出

接下來我們試着在數列中放四個數值,調用函數會出現什么情況呢

>>> l = [3,6,9,1] >>> fun(*l) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() takes exactly 3 arguments (4 given)

在這次調用中我們並沒有得到合適的結果,觸發了TypeWrror異常。很容易看到錯誤內容”fun() takes exactly 3 arguments (4 given)”.

為什么會發生這種情況呢?

數列’l’含有四個數值.因此,我們試圖調用’fun(*l)’,’l’中數值拆開傳給函數fun作為位置參數。但是,’l’中有四個數值,調用’fun(*l)’相當於調用’fun(3,6,9,1)’,又因為函數’fun’定義中只用三個位置參數,因此我們得到這個錯誤。同理,同樣的步驟,數列’l’中有兩個數值情況,注意error內容。

>>> l = [7,4] >>> fun(*l) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() takes exactly 3 arguments (2 given)

‘*l’與位置參數混合使用

>>> fun(23, *l) 23 7 4

在這里,我們給出一個位置參數23,和從數列’l’拆除的兩個數值7和4,因此三個參數23,7和4傳給了函數’fun’

通過一個函數的定義來理解’*args’的含義

修改函數的定義:

>>> def fun(*args): ... print args ... 

傳一個位置參數調用此函數

>>> fun(13) (13,)

傳多個參數調用此函數

>>> fun(11,93,43) (11, 93, 43)

‘*args’在函數定義中是做什么用的?

它接收元組作為位置參數,而非是常見的參數列表。在這里,”args”是個元組。在我們解釋中不要擔心”常見的參數”這部分的理解,這個會在接下來的例子中逐漸明了。在上個例子中,調用函數打印”args”時,他會打印元組中包含的所有數值。

我們重新定義函數,”*args”與”常規參數列表”混合使用

>>> def fun(a, *args): ... print "a is ", a ... print "args is ", args ... 

在這個函數定義中,參數”a”代表”常規參數列表”。 
傳四個位置參數調用此函數:

>>> fun(11,12,34,43) a is 11 args is (12, 34, 43)

很容易看到,’a’打印出為11,即第一個位置參數。’a’之后只一個參數’*args’.因此,’args’接收除常規參數之外的位置參數作為元組。因此元組args作為元組接收12,34和43。

我們也可以傳一個位置參數來調用此函數:

>>> fun(91) a is 91 args is ()

在這里,我們傳的唯一一個參數分配給了常規參數’a’.因此,’args’接收到一個空元組。

既然我們獲取了”args”,我們可以提取需要的數值來做我們想做的事情。重新定義”fun”:

>>> def fun(a, *args): ... print a ... print "args can receive a tuple of any number of arguments, let's print all that." ... for arg in args: ... print arg ... 

現在我們傳任意個參數來調用此函數:

>>> fun(1,5,6,7) 1 args can receive a tuple of any number of arguments, let's print all that. 5 6 7

‘args’既然是元組,我們就可以遍歷它。

現在我們考慮使用所有能得到的參數的場景。我們需要使用兩個函數,第一個函數帶有任意個參數,並通過另外一個函數計算除第一參數的其他參數之和。奇怪的用例,但我們只需回顧我們目前所做的。我們的目的就是在一個函數中獲取可變參數,並把這些參數餐給另一個函數。

第一步我們寫一個函數計算和。在這個用例中,這個函數會在第一個函數中應用。

>>> def calculate_sum(*args): ... return sum(args) ... 

在這個函數中,我們使用內建函數’sum’,它使用元組或數列作為參數,返回元組所有元素的和。從函數的定義可以看出’args’接收包含傳給此函數位置參數的元組.因此,’args’是一個元組,簡介的作為函數’sum’的參數。接下來定義另外一個函數,該函數有任意個參數,並利用上一個函數來計算除第一個參數之外的其他參數的和。

>>> def ignore_first_calculate_sum(a,*iargs): ... required_sum = calculate_sum(*iargs) ... print "sum is ", required_sum ... 

我們可以傳任意個參數給這個函數。第一個參數被常規參數’a’接收,其他參數被’iargs’作為元組接收。正如我們考慮的案例,計算除第一個參數之外的其他參數的和。因此,我們用’a’接收第一個參數,’iargs’是包含其他參數的元組。我們用到函數’calculate_sum’,但’calculate_sum’需要多個位置參數作為元組傳給’args’。所以在函數’ignore_first_calculate_sum’需要拆元組’iargs’,然后將元素作為位置參數傳給’calculate_sum’.注意,用’*’拆元組。

所以,我們這樣調用’required_sum=calculate_sum(*iargs)’.

‘required_sum=calculate_sum(iargs)’不能這么調用,因為傳給’calculate_sum’之前我們需要unpack數值。不使用’*’將不會unpack數值,也就不能執行想要的動作。調用函數如下:

>>> ignore_first_calculate_sum(12, 1,4,5) sum is 10 >>> ignore_first_calculate_sum() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: ignore_first_calculate_sum() takes at least 1 argument (0 given)

得到想要的結果。

通過一個函數的調用來理解’**’的作用

定義一個三個參數的函數,並用多種方式調用:

>>> def fun(a, b, c): ... print a, b, c ... >>> fun(1,5,7) 1 5 7 >>> fun(a=1,b=5,c=7) 1 5 7

使用”**”調用函數,這種方式我們需要一個字典.注意:在函數調用中使用”*”,我們需要元組;在函數調用中使用”**”,我們需要一個字典

>>> d={'b':5, 'c':7} >>> fun(1, **d) 1 5 7

在函數調用中”**”做了什么?

它unpack字典,並將字典中的數據項作為鍵值參數傳給函數。因此,”fun(1, **d)”的寫法與”fun(1, b=5, c=7)”等效. 
為了更好的理解再多舉幾個例子:

>>> d = {'c':3} >>> fun(1, 4, **d) 1 4 3 >>> d = {'a':7, 'b':3, 'c':8} >>> fun(**d) 7 3 8

讓我們制造一些錯誤:

>>> d = {'a':7, 'b':3, 'c':8, 'd':90} >>> fun(**d) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() got an unexpected keyword argument 'd'

這次調用等同於’fun(a=7, b=3, c=8, d=90)’,但函數只需要三個參數,因此我們得到TypeError

>>> d = {'a':7, 'b':3,'d':90} >>> fun(**d) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() got an unexpected keyword argument 'd'

fun(**d)等同於fun(a=7, b=3, d=90).傳給函數”fun”想要的參數個數,但參數列表中並沒有’d’,調用中’d’鍵值參數傳給函數導致TypeError.

So, “*” unpacks the dictionary i.e the key values pairs in the dictionary as keyword arguments and these are sent as keyword arguments to the function being called. “” unpacks a list/tuple i.e the values in the list as positional arguments and these are sent as positional arguments to the function being called.

通過函數定義來理解’**kwargs’的含義

重定義函數”fun”:

>>> def fun(a, **kwargs): ... print a, kwargs ... 

此函數只用一個位置參數,因為常規參數列表中只有一個變量’a’.但是通過”**kwargs”,可以傳多個鍵值參數。

>>> fun(1, b=4, c=5) 1 {'c': 5, 'b': 4} >>> fun(45, b=6, c=7, d=8) 45 {'c': 7, 'b': 6, 'd': 8}

在函數定義中”**kwargs”意味着什么? 
用”**kwargs”定義函數,kwargs接收除常規參數列表職位的鍵值參數字典。在這里’kwargs’是個字典。

重新定義函數:

>>> def fun(a, **kwargs): ... print "a is ", a ... print "We expect kwargs 'b' and 'c' in this function" ... print "b is ", kwargs['b'] ... print "c is ", kwargs['c'] ... >>> fun(1, b=3,c=5) a is 1 We expect kwargs 'b' and 'c' in this function b is 3 c is 5

錯誤調用:

>>> fun(1, b=3, d=5) a is 1 We expect kwargs 'b' and 'c' in this function b is 3 c is Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in fun KeyError: 'c'

上面的調用,位置參數’a’和鍵值參數’b’都打印出來了。傳入的其他鍵值參數是’d’,函數需要鍵值參數’c’,並從字典’kwargs’獲取。但沒有傳入鍵值’c’,引發KeyError.如果傳入了鍵值’c’就不會引發這個錯誤

>>> fun(1, b=3, d=5, c=9) a is 1 We expect kwargs 'b' and 'c' in this function b is 3 c is 9

由於’**kwargs’在函數參數列表中,我們可以傳任意個鍵值參數。上面的調用傳入了”d”,但函數並沒用到。

另外一個錯誤:

>>> fun(1, {'b':2, 'c':34}) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: fun() takes exactly 1 argument (2 given)

正如錯誤提示,函數’fun’只需要一個位置參數,卻給了兩個。盡管’kwargs’接收鍵值參數作為一個字典,但你不能傳一個字典作為位置參數給’kwargs’.你可以像下面那樣調用:

>>> fun(1, **{'b':2, 'c':34}) a is 1 We expect kwargs 'b' and 'c' in this function b is 2 c is 34

在一個字典前使用”**”可以unpack字典,傳字典中的數據項作為鍵值參數。

通過一個應用實例來說明’args’,’kwargs’應用場景以及為何要使用它

在任何時候繼承類和重寫方法的,我們應當用到’*args’和’**kwargs’將接收到的位置參數和鍵值參數給父類方法。通過實例我們更好的理解

>>> class Model(object): ... def __init__(self, name): ... self.name = name ... def save(self, force_update=False, force_insert=False): ... if force_update and force_insert: ... raise ValueError("Cannot perform both operations") ... if force_update: ... print "Updated an existing record" ... if force_insert: ... print "Created a new record" ... 

定義一個類,我們可以創建類的對象,類的對象有一個方法’save()’.假設類的對象可以通過save()方法保存到數據庫中。通過函數save()參數來決定是否在數據庫中創建一條記錄或者更新現存的記錄。 
構造一個新類,類有’Model’的行為,但我們只有檢查一些條件后才會保存這個類的對象。這個新類繼承’Model’,重寫’Model’的’save()’

>>> class ChildModel(Model): ... def save(self, *args, **kwargs): ... if self.name=='abcd': ... super(ChildModel, self).save(*args, **kwargs) ... else: ... return None ... 

實際上對應的保存動作發生在’Model’的’save’方法中。所以我們調用子類的的’save()’方法而非’Model’的方法.子類ChildModel的’save()’接收任何父類save()需要的參數,並傳給父類方法。因此,子類’save()’方法參數列表中有”*args”和”**kwargs”,它們可以接收任意位置參數或鍵值參數,常規參數列表除外。

下面創建ChildModel實體並保存:

>>> c=ChildModel('abcd') >>> c.save(force_insert=True) Created a new record >>> c.save(force_update=True) Updated an existing record

這里傳兼職參數給對象的save()方法。調用的是子類的save(),It received a dictionary containing the keyword argument in “kwargs”. Then it used “**” to unpack this dictionary as keyword arguments and then passed it to the superclass save(). So, superclass save() got a keyword argument ‘force_insert’ and acted accordingly.

轉自:https://blog.csdn.net/callinglove/article/details/45483097

 


免責聲明!

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



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