本篇總結(jié)函數(shù)參數(shù)匹配(argument match)的問題
背景
對于函數(shù)的使用者而言,關(guān)注點(diǎn)有兩個(gè):
1、函數(shù)所提供的接口,即,輸入的問題;
2、函數(shù)的輸出;
輸入--->函數(shù)--->輸出
對于函數(shù)本身而言,關(guān)注點(diǎn)有三個(gè):
1、輸入,包括輸入的格式、輸入的內(nèi)容、校驗(yàn)等;
2、如何處理輸入;
3、提供什么樣的輸出;
輸入--->處理--->輸出
可見,使用者不關(guān)注函數(shù)的處理過程,只關(guān)注輸入和輸出,而函數(shù)則不關(guān)注使用者是誰,只關(guān)注輸入。
那么,可以發(fā)現(xiàn),函數(shù)提供輸入實(shí)際是提供接口,所以函數(shù)輸入設(shè)計(jì)的實(shí)質(zhì)是接口設(shè)計(jì)。
而接口設(shè)計(jì)又可以分為兩部分:形式設(shè)計(jì)和內(nèi)容設(shè)計(jì)。其中,形式設(shè)計(jì)回答什么形式的輸入的問題,內(nèi)容設(shè)計(jì)則回答輸入是什么的問題。
本篇要討論的函數(shù)參數(shù)匹配問題屬于形式設(shè)計(jì)。
好了,形而上的講了一堆東西,下面開始正題。
1. 參數(shù)匹配
首先來個(gè)C++的例子:
假設(shè),有函數(shù)如下:
int record( string name, unsigned int age, string email)
{
// 處理過程
return 0;
}
該函數(shù)所提供的接口為 string name, unsigned int age, string email
那么如下例,使用函數(shù):
record("Joe", 20, "Joe@email.com"); // 正確形式
record(20, "Joe", "Joe@email.com"); // 錯(cuò)誤形式
record("Joe@email.com", 20, "Joe"); // 語法正確,但是內(nèi)容錯(cuò)誤
當(dāng)使用 record(20, "Joe", "Joe@email.com"); 會出現(xiàn)錯(cuò)誤。如果使用IDE那么,這里會提示錯(cuò)誤信息;如果不是使用IDE,那么在編譯時(shí),也會有相應(yīng)的錯(cuò)誤信息提示。錯(cuò)誤的原因是數(shù)據(jù)類型不匹配。
也就是說,在C++中,參數(shù)的匹配時(shí)是按照順序來進(jìn)行的。即:
string name = "Jone",unsigned int age = 20,string email = "Joe@email.com",
對于靜態(tài)強(qiáng)類型語言,當(dāng)出現(xiàn)string name = 20的匹配時(shí),就會出現(xiàn)數(shù)據(jù)類型不匹配的錯(cuò)誤(不考慮模板的情況)。
基本上,常見的編程語言,函數(shù)的默認(rèn)參數(shù)匹配方式都采用順序匹配方式。Python也不例外。但是Python也提供了其他的參數(shù)匹配方式:
位置匹配(順序匹配<默認(rèn)采用這種方式>)
關(guān)鍵字匹配
默認(rèn)值
可變參數(shù)
2. 位置匹配
Python的位置匹配實(shí)例如下:
# 函數(shù)定義
>>> def record(name, age, email):
... record_name = name
... record_age = age
... record_email= email
...
# 函數(shù)使用
>>> record('Joe', 20, 'Joe@email.com') #
>>> record(20, 'Joe', 'Joe@email.com') #形式正確,內(nèi)容錯(cuò)誤
對于動(dòng)態(tài)語言,上例中第二種調(diào)用,并沒有類型不匹配的問題。只是內(nèi)容錯(cuò)誤而已。由此也引出了關(guān)鍵字匹配
3. 關(guān)鍵字匹配
在上述例子中第二種調(diào)用方式之所以會出現(xiàn)問題,原因在于:
由于采用位置匹配,所以為了保證輸入內(nèi)容正確,調(diào)用時(shí),必須知曉參數(shù)的位置(或者順序)。
采用關(guān)鍵字匹配就不存在這種問題。
還是先看例子:
# 函數(shù)定義
>>> def record(name, age, email):
... record_name = name
... record_age = age
... record_email= email
...
# 函數(shù)使用
>>> record(name = 'Joe', age = 20, email = 'Joe@email.com') #
>>> record(age = 20, name = 'Joe', email = 'Joe@email.com') #
可以看到,關(guān)鍵字匹配與位置匹配的最大區(qū)別在于:關(guān)鍵字匹配,在調(diào)用函數(shù)時(shí),加入了實(shí)參與形參的對應(yīng)信息。
這樣做的好處在于,調(diào)用者只需要知道調(diào)用時(shí)需要輸入的參數(shù)名稱即可,不用理會其順序。另外,從語義的角度來看,這種方式在表達(dá)上也更加清晰,因?yàn)檎{(diào)用時(shí),十分清楚的寫明了:
age = 20,name = 'Joe',email = 'Joe@email.com',
程序的可讀性非常高。
4. 默認(rèn)值
對于函數(shù)的調(diào)用者而言,調(diào)用時(shí)所需的參數(shù)越少,意味著使用起來也越簡單。但是,有時(shí)候,為了功能的靈活,同時(shí)需要保留足夠的備選參數(shù)。這些備選參數(shù)需要有以下特點(diǎn):
- 如果調(diào)用時(shí)不設(shè)定,則為默認(rèn)值;
- 如果調(diào)用時(shí)設(shè)定,則使用設(shè)定值;
這樣,使得函數(shù)的易用性與靈活性同時(shí)得到保證。默認(rèn)值參數(shù)就實(shí)現(xiàn)了這樣的功能。
舉例:
>>> def record(name, age, email="xxx@email.com"):
... print name, age, email
...
>>> record('Joe', 20, 'Joe@gmail.com')
Joe 20 Joe@gmail.com
>>> record('Joe', 20)
Joe 20 xxx@email.com
5. 可變參數(shù)
為什么需要可變參數(shù)
假設(shè)現(xiàn)在需要這樣一個(gè)函數(shù):輸入兩個(gè)字符串,輸出兩個(gè)字符串拼起來的字符串。
>>> def str_add(str1, str2):
... return str1 + str2
...
>>> str_add("I am ", "Joe ")
'I am Joe '
輸入兩個(gè)字符串的例子很容易實(shí)現(xiàn),那么問題來了... ....
如果要求輸入3個(gè)字符串呢?4個(gè)?5個(gè)?... ...總不能一個(gè)函數(shù)一個(gè)函數(shù)的寫下去吧。
總結(jié)一下,很容易發(fā)現(xiàn)不論輸入是幾個(gè)字符串,所作的操作都是將所輸入的字符串拼起來。這個(gè)時(shí)候,可變參數(shù)就派上用場了。
先看例子:
>>> def str_add(*str): # 定義可變參數(shù)
... print reduce(lambda x,y:x+y,str) # 將str中的所有變量相加
...
>>> str_add("I am ", "Joe")
I am Joe
>>> str_add("I am ", "Joe, ","my email is ","Joe@gmail.com")
I am Joe, my email is Joe@gmail.com
>>>
上例中,采用可變參數(shù)的函數(shù)str_add可以支持不定個(gè)數(shù)的字符串輸入,輸出這些字符串拼接之后的字符串。
由此可見:
可變參數(shù)的意義在于,能夠使函數(shù)處理參數(shù)不定,操作具有共性的需求。而所謂的可變參數(shù)是指,參數(shù)的個(gè)數(shù)是可以變化的。
定義可變參數(shù)
弄清楚為什么要使用可變參數(shù)以后,我們來說一下上面的例子中是如何定義可變參數(shù)的。
首先,我們使用Tuple可以實(shí)現(xiàn)一個(gè)功能與可變參數(shù)相同的函數(shù)。
>>> def str_add(str):
... print reduce(lambda x,y:x+y, str)
...
>>> words = ("I am ", "Joe") #創(chuàng)建一個(gè)Tuple對象
>>> str_add(words)
I am Joe
對比這個(gè)例子和上面的例子可以發(fā)現(xiàn),在本例中,需要提前構(gòu)造一個(gè)Tuple對象,然后再將這個(gè)對象傳入函數(shù)。
實(shí)際上,上一節(jié)的例子中str_add函數(shù)內(nèi)部 *str接收到也是Tuple對象。
在Python中如果List對象或者Tuple對象使用*來修飾作為實(shí)參傳入函數(shù)時(shí),會將其變成可變參數(shù)列表傳入函數(shù)。
比如下面的例子:
>>> def str_add(*str):
... print reduce(lambda x,y:x+y,str)
...
>>> words = ("I am ", "Joe")
>>> str_add(*words) #在Tuple對象words前加*作為實(shí)參傳入函數(shù)
I am Joe
可變參數(shù)在形式上有兩種:
- 對于List對象或者Tuple對象,在變量前加
*; - 對于Dict對象,在變量前加
**;
在Python中稱加*的變量為Non-keyword Variable Arguments,稱加**的變量為keyword Variable Arguments。
下面給出一個(gè)可變參數(shù)使用的完整例子
>>> def foo(name, age, *args, **kwargs):
... print name, age, args, kwargs
...
>>> info = ["Joe@gmail.com","male"]
>>> addition = {"location":"Beijing","prof":"software"}
>>> foo("Joe",20, *info, **addition) #List對象info前加*,Dict對象addition前加**
Joe 20 ('Joe@gmail.com', 'male') {'prof': 'software', 'location': 'Beijing'}
>>> foo("Joe",20, "Joe@gmail.com","male",location="Beijing",prof="software")
Joe 20 ('Joe@gmail.com', 'male') {'prof': 'software', 'location': 'Beijing'}
總結(jié)一下:
Python的函數(shù)參數(shù)匹配提供了多種方式,非常靈活,使用的時(shí)候即可以單獨(dú)使用某一種方式,也可以混合使用多種方式。