理解’*’,’*args’,’**’,’**kwargs’

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

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

>>> fun(23, *l)
23 7 4
  • 1
  • 2
  • 1
  • 2

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

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

修改函式的定義:

>>> def fun(*args):
...     print args
... 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

傳一個位置引數呼叫此函式

>>> fun(13)
(13,)
  • 1
  • 2
  • 1
  • 2

傳多個引數呼叫此函式

>>> fun(11,93,43)
(11, 93, 43)
  • 1
  • 2
  • 1
  • 2

‘*args’在函式定義中是做什麼用的?

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

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

>>> def fun(a, *args):
...     print "a is ", a
...     print "args is ", args
... 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

在這個函式定義中,引數”a”代表”常規引數列表”。 
傳四個位置引數呼叫此函式:

>>> fun(11,12,34,43)
a is  11
args is  (12, 34, 43)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

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

我們也可以傳一個位置引數來呼叫此函式:

>>> fun(91)
a is  91
args is  ()
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

在這裡,我們傳的唯一一個引數分配給了常規引數’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
... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

現在我們傳任意個引數來呼叫此函式:

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

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

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

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

>>> def calculate_sum(*args):
...     return sum(args)
... 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

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

>>> def ignore_first_calculate_sum(a,*iargs):
...     required_sum = calculate_sum(*iargs)
...     print "sum is ", required_sum
... 
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

我們可以傳任意個引數給這個函式。第一個引數被常規引數’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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

得到想要的結果。

通過一個函式的呼叫來理解’**’的作用

定義一個三個引數的函式,並用多種方式呼叫:

>>> 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

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

在函式呼叫中”**”做了什麼?

它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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

讓我們製造一些錯誤:

>>> 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

這次呼叫等同於’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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

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
... 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

此函式只用一個位置引數,因為常規引數列表中只有一個變數’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}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

在函式定義中”**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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

錯誤呼叫:

>>> 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'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的呼叫,位置引數’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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

由於’**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)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

正如錯誤提示,函式’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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

在一個字典前使用”**”可以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"
... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

>>> class Child(Model):
...     def save(self, *args, **kwargs):
...             if self.name=='abcd':
...                     super(ChildModel, self).save(*args, **kwargs)
...             else:
...                     return None
... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

實際上對應的儲存動作發生在’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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

這裡傳兼職引數給物件的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.

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