1. 創建函數
一個函數代表一個行為并且返回一個結果(包括None),在Python中使用def
關鍵字來定義一個函數,如下:
def hello(name):print 'hello,' + name + '!'
?接下來調用函數,并查看其返回值:
# output: # hello,gy! # None print hello('gy')
可以看到hello
函數首先打印了:Hello,gy!
,然后我們又將其返回值也打印出來,在這里因為沒有返回具體的值,所以返回了None
。
?
下面我們可以定義一個用于計算斐波那契數列的函數,接收計算的位數(參數),返回計算的結果(返回值),如下:
1 def fibs(num): 2 result = [0,1] 3 for i in range(num - 2): 4 result.append(result[-2] + result[-1]) 5 return result 6 7 # output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 8 print fibs(10)
?
2. 文檔字符串
文檔字符串指的是在函數開頭寫下該函數的說明字符串,該字符串會作為函數的一部分進行存儲。可以通過調用__doc__
屬性來查看:
1 def fibs(num): 2 "根據指位數來計算斐波那契數列" 3 result = [0,1] 4 for i in range(num - 2): 5 result.append(result[-2] + result[-1]) 6 return result 7 8 # 輸出:根據指位數來計算斐波那契數列 9 print fibs.__doc__
?
3. 參數
函數使用參數來傳遞信息,而參數類型又基本分為兩大類型:可變類型參考和不可變類型參數。
?
3.1 不可變類型參數
不可變類型(字符串、數字和元組等)是不可變的,即無法修改,只能使用新的值來覆蓋。使用不可變類型作為函數的參數時:在函數內為參數賦值不會改變其外部變量的值,如下:
1 # 字符串是不可變的 2 def try_to_change(n): 3 n = 'Gumby' 4 5 name = 'Sunshine' 6 7 # 嘗試改變參數的值 8 try_to_change(name) 9 10 # output: Sunshine(函數內部的改變對其沒有影響) 11 print name
?
3.2 可變類型參數
可變類型參數(列表等)指的是:使用可變的數據結構來作為函數的參數使用。在函數內部修改可變類型參數的值時,會同時改變其外部變量的值(因為它們引用的其實是同一個對象),如下所示:
1 # 列表是可變的 2 def try_to_change2(n): 3 n[0] = 'Sunshine' 4 5 person = ['Gumby','Bob'] 6 # 嘗試改變參數的值 7 try_to_change2(person) 8 9 # output: ['Sunshine', 'Bob'] 10 # 函數內部的改變會作用于外部的變量 11 print person
?
如果想避免上面的情況可以對需要作為可變類型的參數的對象復制一個副本,這里是列表可以通過對其進行切片來返回一個新的副本,如下:
1 # 將可變的數據結構(列表)用作參數 2 def try_to_change2(n): 3 n[0] = 'Sunshine' 4 5 person = ['Gumby','Bob'] 6 n = person[:] # 通過切片返回新的副本 7 try_to_change2(n) 8 print person # ['Gumby', 'Bob'] 9 print n # ['Sunshine', 'Bob'] 10 print n is person # False
?
3.3 關鍵字參數和默認值
在我們之前使用參數的形式是:位置參數,即調用該函數給其傳值時,是根據輸入的值的先后順序來給參數一一賦值 的。不過有時候如果我們需要定義的函數有大量的參數時,傳值時只是于依賴于順序的話是比較容易出錯的。同時如果部分參數在不多數情況下值都是一樣的,每次 調用都需要重新賦值也比較麻煩。針對于這種情況Python為我們提供了兩個語法糖:即關鍵字參數和默認值。(PS:C#4.0新增的兩個特性:具名參數和可選參數,和Python的這兩個語法糖很類似,感興趣的同學可以參考這里:《Effective C#》讀書筆記——條目10:使用可選參數減少方法重載的數量<C#語言習慣>)。
使用關鍵字參數不需要對函數進行任何修改,只需要在調用函數時顯示為參數賦值即可(不依賴特定順序),如下:
1 def hello(gretting,name): 2 print "%s,%s!" % (gretting,name) 3 4 # output: hello,world! 5 hello(name='world',gretting='hello')
?
可以看到雖然調用時代碼變多了,但是每個參數的含義一目了然。不過有時候我們的函數有些參數在大部分情況下使用同樣的值,有時候也需要改變,這時候使用默認值,可以很好的解決這種問題,如下:
1 def hello(gretting = 'hello',name='world'): 2 print "%s,%s!" % (gretting,name) 3 4 hello() # hello,world! 5 hello(name = 'Sunshine') # hello,Sunshine! 6 hello('Greetings') # Greetings,world!
?可以看到,如果沒有給任何值的話,函數自動使用默認值,也可以通過關鍵字參數指定部分值,其他的依然使用默認參數,這為我們的方法調用提供了很大的靈活性。
?
3.4 使用任意參數列表
前面介紹的參數的個數基本都是固定的,有時候可以接收任意多的參數也是很有必要的,這時候可以使用Python為我們提供的語法糖(*
號)來實現:
1 def print_params(number,*params): 2 print number 3 print params 4 5 # output: 6 # 6 7 # (1, 2, 3, 4, 5, 6) 8 print_params(6,1,2,3,4,5,6) 9 10 # output: 11 # 0 12 # () 13 print_params(0)
?
我們可以看到參數前的*
號將所有的值放置到一個元組中(可以理解為將其他的參數收集起來)。如果沒有為*
號后的參數賦值則其值為一個空的元組。不過使用一個*
號這樣的語法不能處理關鍵字參數,如下:
1 def print_params(*params): 2 print params 3 4 # TypeError: print_params() got an unexpected keyword argument 'params' 5 print_params(params = (1,2,3))
?
解決的辦法很簡單,使用**
來修飾參數即可,如下所示:
1 # -- coding: utf-8 -- 2 def print_params(**params): 3 print params 4 print type(params) 5 6 # output: {'params': (1, 2, 3)} 7 # output: <type 'dict'> 8 print_params(params = (1,2,3))
?可以看到使用**
修飾的參數本質上其實是一個字典類型。可以通過這個字典來收集參數。
?
3.5 解包參數列表
除了通過才定義函數的參數(形參)前添加*
或者**
來收集參數,我們還可以在調用函數時在實參前調解這兩個操作符,它們表示其對應的逆過程。如下所示:
1 def add(x,y): 2 print x + y 3 4 params = (1,2) 5 6 # output: 3 7 add(*params)
?
通過使用*
操作符我們將params參數分割了add
函數所需的兩個參數,同樣我們也可以使用**
操作符來分割字典對象:
1 def hello(greeting,name): 2 print '%s,%s' %(greeting,name) 3 4 params = {'greeting':'hello','name':'world'} 5 6 # output: hello,world 7 # 這里使用*操作符也是可以的 8 hello(**params)
?
參考資料&進一步閱讀
Python基礎教程(第二版)
Python入門教程——深入函數定義