函數聲明、調用、返回基礎
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聲明函數的過程中,在函數被調用之前,函數所記錄的變量一直都是變量的地址,或者通俗一點理解為記錄變量的名稱,而不會進行變量的賦值替換。
實際上,變量的明確的值會當作常量被記錄起來。如a=10
的10被作為常量,而變量a賦值給變量b時b=a
,a顯然不會作為常量。
如下函數:
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的人來說,可能會很容易理解,但對於學過其它語言的人來說,很容易混淆出錯。
此處並不會對這些示例的結果進行解釋,因為只要理解了前文函數變量的細節,這幾個示例的結果很容易理解。
同樣,更詳細的內容參見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