Python函式基礎
函式宣告、呼叫、返回基礎
Python中使用def關鍵字來宣告函式,宣告函式的格式為:
def func_name(args):
...body...
[return ...]
有3個需要注意的地方:
- 函式名後面必須加冒號
- 如果函式體和def不在同一行,則必須縮排
- return指定函式返回值,用來結束函式
- 但return語句是可有可無的,如果不給return,則等價於加上了
return None
,即函式預設返回None結構
- 但return語句是可有可無的,如果不給return,則等價於加上了
如果函式體body語句只有一行,或者可以簡寫為一行,則可以寫在def的同行。例如:
def myfunc(x,y,z): print(x+y+z)
函式宣告好之後,就可以執行函式,執行函式也稱為呼叫函式,方式為func_name(args)
,例如:
myfunc(1,2,3)
函式中往往會包含一個return或多個return語句,它可以出現在函式中的任意位置處,它用來結束函式的執行,並返回給定的值。例如:
def func(x):
return x+5
表示返回x+5
的值,返回值是一種值型別,所以可以賦值給變數、可以輸出、可以操作等等。例如:
print(func(3)) # 輸出返回值
a=func(4) # 賦值給變數
print(a)
print(func(5)+3) # 數值操作
return語句是可選的,如果函式中不指定return語句,則預設返回None,即類似於return None
關於函式引數
函式的引數其實也是變數,只不過這些變數是獨屬於函式的本地變數,函式外部無法訪問。在函式呼叫的時候,會將給定的值傳遞給函式的引數,這實際上是變數賦值的過程。
def myfunc(x,y,z):
print(x,y,z)
myfunc(1,2,3)
def首先宣告好函式,然後到了myfunc(1,2,3)
時,表示呼叫函式(執行函式),呼叫函式時會將給定的值1,2,3
傳遞給函式的引數x,y,z
,其實就是變數賦值x=1,y=2,z=3
,然後使用print輸出它們。
由於python是動態語言,無需先宣告變數,也無需指定變數的型別,所以python的函式引數和返回值非常的靈活。任何型別的變數或資料結構都可以傳遞給引數,這實際上是變數賦值的過程。例如:
myfunc(1,2,3)
myfunc("abc",2,"def")
myfunc([1,2,3],4,5)
上面幾個函式呼叫語句中,賦值給引數的值可以是數值型別,可以是字串型別,可以是列表型別,也可以是其它任何資料型別。
python函式的引數相比其它語言要複雜一些,意味著要靈活很多,短短一個小節的篇幅完全沒法解釋清楚,關於引數細節,詳細內容見後面的文章。
函式宣告、呼叫的過程詳述
def用來宣告一個函式,python的函式包括函式名稱、引數、函式體、函式體中涉及到的變數、返回值。
實際上,函式名稱其實是一個變數名,def表示將儲存在某塊記憶體區域中的函式程式碼體賦值給函式名變數。例如:
def myfunc(x,y,z):
...CODE...
上面表示將函式體賦值給變數名myfunc。如下圖:
既然是變數,就可以進行輸出:
def myfunc(x):
return x+5
print(myfunc)
輸出結果:
<function myfunc at 0x032EA6F0>
由於python是解釋型語言,所以必須先定義函式,才能呼叫函式。
如果匯入一個模組檔案,匯入的時候會解釋、執行檔案中的程式碼,包括def語句,也就是說,匯入檔案時會先宣告好函式。
函式變數的細節
請一定理解本節內容,也許細節方面可能會有些不準確,但對於深入理解函式來說(不僅限於python語言),是非常有幫助的,特別是理解作用域規則的時候。
python是解釋性語言,讀一行解釋一行,解釋一行忘記一行。而函式是一種程式碼塊,程式碼塊是一個解釋單元,是一個整體。在程式碼塊範圍內不會忘記讀取過的行,也不會讀一行就立即解釋一行,而是讀取完所有程式碼塊內的行,然後統籌安排地進行解釋。關於這一點,在後面的文章程式碼塊詳述中有非常詳細的解釋,建議一讀。
當python讀取到def所在行的時候,知道這是一個函式宣告語句,它有一個屬於自己的程式碼塊範圍,於是會讀完整個程式碼塊,然後解釋這個程式碼塊。在這個解釋過程中,會記錄好變數以及該變數的所屬作用域(是全域性範圍內的變數還是函式的本地變數),但一定注意,def宣告函式的過程中不會進行變數的賦值(引數預設值除外,見下文),只有在函式呼叫的時候才會進行變數賦值。換句話說,在def宣告函式的過程中,在函式被呼叫之前,函式所記錄的變數一直都是變數的地址,或者通俗一點理解為記錄變數的名稱,而不會進行變數的賦值替換。
如下函式:
x=3
def myfunc(a,b):
c=10
print(x,a,b,c)
myfunc(5,6)
輸出結果為:"3 5 6 10"。
上面的函式涉及到了4個變數:a、b、c、x。其中:
- 全域性變數x
- 本地變數a、b、c,其中本地變數a和b是函式的引數
在def的過程中,會完完整整地記錄好這些變數以及所屬作用域,但只會記錄,不會進行變數的賦值。如下圖:
然後函式被呼叫,這時候才會開始根據記錄的作用域搜尋變數是否存在,是否已經賦值(非本地變數),並對需要賦值的變數賦值:
- 查詢全域性變數變數x,它在全域性作用域內已經賦值過了,所以只需找到這個全域性變數即可
- 查詢本地變數a、b、c,它們是屬於函式myfunc的本地變數,而a和b是引數變數,所以最先對它們進行賦值
a=5,b=6
,然後賦值普通的本地變數c=10
如圖:
最後執行print(x,a,b,c)
輸出這些變數的值。
還需注意,python是讀一行解釋一行的,在函式呼叫過程中,因為c=10
在print()
的前面,所以是先賦值c=10
,再執行print,如果print在c=10
前面,則先執行print,再賦值,這顯然是錯誤的,因為print()中使用了變數c,但目前還沒有對其賦值。這和其它語言可能有些不同(特別是編譯型語言),它們可能會無視變數賦值以及變數使用的位置前後關係。
如果上面的示例中,函式myfunc呼叫之前,將變數x賦值為另一個值:
x=3
def myfunc(a,b):
c=10
print(x,a,b,c)
x=33
myfunc(5,6)
這時將輸出:"33 5 6 10"。因為x是全域性變數,只有在函式呼叫的時候才會去找到變數x對應的值,而這時全域性變數的值已經是33。
匿名函式lambda
匿名函式是指沒有名稱的函式,任何程式語言中,匿名函式都扮演著重要角色,它的功能非常靈活,但是匿名函式中的邏輯一般很簡單,否則直接使用命名函式更好,匿名函式常用於回撥函式、閉包等等。
在python中使用lambda關鍵字宣告匿名函式,python中的lambda是一個表示式而不是一個語句,這意味著某些語句環境下可能無法使用def宣告函式,但卻可以使用lambda宣告匿名函式。當然,匿名函式能實現的功能,命名函式也以一樣都能實現,只不過有時候可能會比較複雜,可讀性會更差。
lambda宣告匿名函式的方式很簡單,lambda關鍵字後面跟上引數列表,然後一個冒號,冒號後跟一個表示式。
lambda argl, arg2,... argN :expression statement
lambda表示式返回一個匿名函式,這個匿名函式可以賦值給一個變數。
例如:
# 宣告匿名函式,並賦值給變數f
f = lambda x,y,z: x+y+z
print(f)
輸出結果:
<function <lambda> at 0x027EA6F0>
既然匿名函式賦值給了變數,這個函式就像是命名變數一樣,可以通過這個變數去呼叫這個匿名函式。當然,它畢竟還是匿名函式,正如上面輸出的結果中function <lambda>
所示。而且,匿名函式並非一定要賦值給變數。
# 呼叫匿名函式
print(f(2,3,4)) # 輸出9
匿名函式的返回值是冒號後面的表示式計算得到的結果。對於上面的示例,它等價於return x+y+z
。
因為lambda是一個表示式,所以可以寫在任何表示式可以出現的位置處,而某些語句上下文環境中,並不能直接使用def來宣告。例如,將函式儲存到一個列表中:
L=[ lambda x: x * 2,
lambda x: x * 3,
lambda x: x * 4 ]
print(L[0](2))
print(L[1](2))
print(L[2](2))
上面的lambda出現在列表的內部,且這裡面的匿名函式並賦值給某個變數。像def語句就無法出現在這樣的環境中,如果真要使用def來宣告函式,並儲存到列表中,只能在L的外部使用def定義,然後將函式名來儲存。
def f1(x): return x * 2
def f2(x): return x * 3
def f3(x): return x * 4
L=[f1,f2,f3]
print(L[0](2))
print(L[1](2))
print(L[2](2))
看上去沒什麼問題,但函式定義的位置和列表L定義的位置可能會相差甚遠,可讀性可能會非常差。
同理的,還可以將匿名函式儲存在字典的value位置上:
key='four'
print(
{
'two':(lambda x: x * 2),
'three':(lambda x: x * 3),
'four':(lambda x: x * 4)
}[key](2)
)
函式巢狀
函式內部可以巢狀函式。一般來說,在函式巢狀時,內層函式會作為外層函式的返回值(當然,並非必須)。既然內層函式要作為返回值,這個巢狀的內層函式更可能會是lambda匿名函式。
例如:
def f(x):
y=10
def g(z):
return x+y+z
return g
上面的函式f()中嵌套了一個g(),並返回這個g()。其實上面示例中的g()是一個閉包函式。
既然f()返回的是函式,這個函式可以賦值給其它變數,也可以直接呼叫:
# 將巢狀的函式賦值給變數myfunc
# 這時myfunc()和g()是等價的
myfunc = f(3)
print( myfunc(5) )
# 直接呼叫g()
print( f(3)(5) )
當然,巢狀lambda匿名函式也可以,且更常見:
def f(x):
y=10
return lambda z: x+y+z
巢狀在迴圈內部的函式
看下面巢狀在迴圈內部的函式,在每個迭代過程中都宣告一個匿名函式,這個匿名函式返回迴圈控制變數i,同時將宣告的匿名函式儲存到列表L中。
def f():
L=[]
for i in range(5):
L.append( lambda : i )
return L
但如果呼叫該函式f(),並呼叫儲存在列表中的每個匿名函式,會發現它們的值完全相同,且都是迴圈迭代的最後一個元素值i=4
。
List = f()
print(List[0]())
print(List[1]())
print(List[2]())
print(List[3]())
print(List[4]())
執行結果:
4
4
4
4
4
為什麼會如此?為什麼迴圈迭代過程中的i沒有影響到匿名函式的返回值?這是一個非常值得思考的問題,如果不理解結果,請仔細回顧前文函式變數的細節。如果還是不理解,請閱讀Python作用域詳述。
巢狀函式的作用域
此處給幾個示例,這些示例的結果對於只學過python的人來說,可能會很容易理解,但對於學過其它語言的人來說,很容易混淆出錯。
此處並不會對這些示例的結果進行解釋,因為只要理解了前文函式變數的細節,這幾個示例的結果很容易理解。
如下示例:
x=3
def f():
x=4
g()
print("f:",x)
def g():
print("g:",x)
f()
輸出:
g: 3
f: 4
如果在呼叫函式前,修改全域性變數x的值:
x=3
def f():
x=4
g()
print("f:",x)
def g():
print("g:",x)
x=6
f()
輸出:
g: 6
f: 4
如果把g()的宣告放在f()的內部呢?
x=3
def f():
x=4
def g():
print("g:",x)
print("f:",x)
x=5
return g
f()()
輸出:
f: 4
g: 5