給妹子講python-S01E21函式引數的匹配與解包詳解

醬油哥在掘金發表於2019-01-16

歡迎關注公眾號:python資料科學家

【要點搶先看】

1.基於位置和關鍵字的引數匹配
2.使用預設引數形式
3.函式定義使用 * 和 ** 進行任意數目引數收集
4.函式呼叫時使用 * 和 ** 進行引數解包

正如我們之前所講的,引數在python中總是通過賦值進行傳遞的。在預設情況下,引數是通過其位置進行匹配的,從左到右,而且必須精確的傳遞和函式頭部引數名一樣多的引數。

這種預設的傳遞方式很簡單

def f(a,b,c):
    print(a,b,c)

f(1,2,3)

1 2 3
複製程式碼

python中可以使用基於關鍵字的引數匹配形式。在呼叫函式的時候,能夠更詳盡的定義內容傳遞的位置。關鍵字引數允許通過變數名進行匹配,而不是通過位置。

def f(a,b,c):
    print(a,b,c)

f(c=3,a=1,b=2)

1 2 3
複製程式碼

我們可以看出,當關鍵字引數使用時引數從左至右的關係已不再重要了,因為引數是通過變數名進行傳遞的,而不是通過其位置。這種呼叫顯得更文件化一些(例如使用一些名稱更直觀的引數名)。

甚至在一個呼叫中混合使用基於位置的引數和基於關鍵字的引數也可以。在這種情況下,所有基於位置的引數首先按照從左到右的順序匹配頭部的引數,之後再進行基於變數名進行關鍵字的匹配。

def f(a,b,c):
    print(a,b,c)

f(1,c=3,b=2)

1 2 3
複製程式碼

再來說說python中的預設引數形式。預設引數允許建立函式可選的引數,如果沒有傳入值的話,在函式執行前,引數就被賦予了預設值,還是看一個例子:

def f(a,b=2,c=3):
    print(a,b,c)

f(1)
1 2 3

f(a=1)
1 2 3
複製程式碼

看到這個例子中,我們必須為a提供值,無論是通過位置引數還是關鍵字引數來實現,然而,為b和c提供值是可選的。如果我們不給b和c傳遞值,它們會分別賦值為2和3。

那麼按位置順序,當給函式傳遞兩個值的時候,只有c得到預設值,並且當有三個值傳遞時,不會使用預設值。

def f(a,b=2,c=3):
    print(a,b,c)

f(1,4)

1 4 3

def f(a,b=2,c=3):
    print(a,b,c)

f(1,4,8)

1 4 8
複製程式碼

最後一種情況,是關鍵字和預設引數一起使用的情況。關鍵字引數允許我們跳過有預設值的引數,但是要記住的是,首先要完成通過位置進行匹配的引數。

def f(a,b=2,c=3):
    print(a,b,c)

f(1,c=6)

1 2 6
複製程式碼

很明顯,a通過位置得到了1,c通過關鍵字得到了6,而跳過了b,b通過預設引數得到了2.

【妹子說】我有時看到函式定義的引數中有 * 和 ** 符號,表示什麼意思?

這也是python中非常有特色的:當 * 和 ** 符號出現在函式定義的引數中時,表示任意數目引數收集。

先說說 * ,他是用元組的形式收集不匹配的位置引數。當這個函式呼叫時,python將所有位置相關的引數收集到一個新的元組中,並將這個元組賦值給變數args。

def f(a,*args):
    print(args)

f(1,2,3,4)

(234)
複製程式碼

再說說 ** 的特性,他只對關鍵字引數有效。在這種情況下, ** 允許將關鍵字引數轉化為字典,你能夠在之後使用鍵呼叫來進行步進或字典迭代

def f(**kargs):
    print(kargs)

f(a=1,b=2)

{'b'2'a'1}
複製程式碼

最後我們來概況一下最一般的形式。即在函式頭部能混合一般引數、 * 引數以及 ** 去實現更加靈活的呼叫方式。

def f(a, *pargs, **kargs):
    print(a,pargs,kargs)

f(1,2,3,x=4,y=5)

1 (23) {'x'4'y'5}
複製程式碼

這個例子中,1按位置傳給a,2和3收集到pargs位置的元組中,x和y放入kargs關鍵字詞典中

上面是在函式定義的時候寫的 * 和 ** 形式,那反過來,如果 * 和 ** 語法出現在函式呼叫中又會如何呢?他會解包引數的集合。

例如,我們在呼叫函式時能夠使用 * 語法,在這種情況下,它與函式定義的意思相反,他會解包引數的集合,而不是建立引數的集合。例如我們可以通過一個元組給一個函式傳遞四個引數,並且讓python將它們解包成不同的引數。

def func(a,b,c,d):
    print(a,b,c,d)

args = (1,2,3,4)
func(*args)

1 2 3 4
複製程式碼

相似的,在函式呼叫時,**會以鍵/值對的形式解包一個字典,使其成為獨立的關鍵字引數。

def func(a,b,c,d):
    print(a,b,c,d)

kargs =
 {'a':1'b':2'c':3'd':4}
func(**kargs)

1 2 3 4
複製程式碼

當然這種形式也是可以混合的,從左到右要以位置引數、元組解包、關鍵字引數、字典解包的順序來進行

def func(a,b,c,d,e,f):
    print(a,b,c,d,e,f)

args = (2, 3)
kargs =
 {'d'4'e'5}

func(1, *args, f=6, **kargs)

1 2 3 4 5 6
複製程式碼

說了這些基本的知識,我們來看看一個實際應用的例子

這種支援任意引數形式的方法的一個應用點,就是實現對其他函式進行靈活呼叫。因為引數列表可以通過元組、字典形式傳入,所以他支援執行時構建引數列表,這對於測試和計時其他函式非常方便。在下面的程式碼中,我們通過傳遞任何傳送進來的引數來支援具有任意引數的任意函式:

def tracer(func, *args, **kargs):
    print('calling:', func.__name__)
    return func(*args, **kargs)

def func(a,b,c,d):
    return a+b+c+d

print(tracer(func,1,2,c=3,d=4))

calling: func
10
複製程式碼

很明顯,這裡在定義tracer函式時應用了收集任意引數的方法,在其中呼叫func函式時又利用瞭解包引數的方法。

最後綜合所學,我們舉一個例子,來仿寫一個函式模擬print的功能,他可以接收任意的位置引數,同時接收規定範圍內的關鍵字引數,對多餘的關鍵字引數會報錯。

import sys
def print30(*args, **kargs):
    sep = kargs.pop('sep'' ')
    end = kargs.pop('end''\n')
    file = kargs.pop('file', sys.stdout)
    if kargs:
        raise TypeError('extra keywords:{}'.format(kargs))
    output = ''
    first = True
    for arg in args:
        output += ('' if first else sep) + str(arg)
        first = False
        file.write(output + end)

print30('hello','world','healthy',sep='&')

hello&world&healthy
複製程式碼

在這段程式中,有幾個關鍵點值得我們注意

對kargs字典進行pop操作,彈出了指定的三個關鍵字sep、end、file後,如果字典裡還有值,則證明是多餘的關鍵字,程式需要報錯。第二在pop的時候,如果這三個引數如果在呼叫函式的時候指定了值,就用指定的值,如果沒有指定值則用程式中指定的預設值。

從下面的例子就可以看出,使用多餘的關鍵字,程式會報錯。

print30('hello','world','healthy',sep='&',ppp='33',hhh='44')

Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 15in <module>
   print30('hello','world','healthy',sep='&',ppp='33',hhh='44')
 File "E:/12homework/12homework.py", line 7in print30
raise TypeError('extra keywords:{}'.format(kargs))
TypeError: extra keywords:{'ppp''33''hhh''44'}
複製程式碼

公眾號二維碼:python資料科學家:

給妹子講python-S01E21函式引數的匹配與解包詳解

相關文章