函數是把一些語句集合在一起的程序結構,用於把復雜的流程細分成不同的組件,能夠減少代碼的冗余、代碼的復用和修改代碼的代價。
函數可以0個、1個或多個參數,向函數傳遞參數,可以控制函數的流程。函數還可以返回代碼執行的結果,從技術上講,任何函數都要返回結果,一個沒有返回值的函數會自動返回none對象。如果調用者需要函數返回結果,需要顯式使用return語句。
一,函數的定義
Python使用def 語句將創建一個函數對象,並將其賦值給一個變量名,()內是函數的參數,參數通過賦值向參數傳值。
def fun_name (arg1,age2...): fun_body
return語句表示函數調用的結束,並把結果傳遞給調用者。return語句是可選的,如果它沒有出現,那么函數將會在控制流執行完函數主體時結束。
1,def是可執行的語句
def語句是實時執行的,不僅def語句,Python中的所有語句都是實時運行的,並沒有獨立的編譯時間。def語句是一個可執行的語句,在def執行前,函數並不存在,直到def語句執行之后,函數才被創建。
2,函數是一個對象,函數名是變量
def語句是一個賦值語句,函數是一個對象,函數名是一個變量,def語句設置函數名和函數對象的引用。
當def語句運行的時候,它創建了一個新的函數對象,並將其賦值給一個變量名。
3,參數是通過賦值傳遞的
Python通過賦值(=)把參數傳遞給函數,這和普通的賦值語句(a=1,或a=b)的行為是相同。當傳遞常量給參數時,參數引用的是一個全新的對象;當傳遞變量給參數,參數和變量是對象的共享引用。
4,函數是可以嵌套的
一個def是一個語句,可以出現在任一語句可以出現的地方,甚至嵌套在其他的語句中。這就是說,函數內部可以嵌套函數的定義,例如:
def times(x,y): z=x*y def print_result(): print(z) print_result()
函數 print_result()是一個嵌入函數,定義在函數times(x,y)之內。
實際上,Python的函數是有層次結構,最外層的def是頂層函數,在頂層函數內部定義的函數是嵌套函數。
二,函數的調用
函數通過表達式調用,傳入一些值,並返回結果。函數調用的格式是:函數名 + (args),小括號中是傳遞給參數的變量或值,例如:
func_name(var1,var2...)
如果函數存在返回值,使用變量來接收函數的返回值:
ret_value=fun_name(var1,var2...)
在調用函數時,函數的行為依賴於類型,這是因為函數是語句的集合,語句包含操作符,而操作符的行為是依賴於類型的,
例如,函數times返回兩個參數的乘積,當傳入數字時,函數 times(2,4) 返回8,* 的作用是計算乘積;當傳入字符類型時,函數 times('ab',2) 返回'abab',*號的作用是重復字符串。換句話說,函數times()的作用取決於傳遞給它的值。
def times(x,y): return x *y
調用函數時,這種依賴於類型的行為稱為多態,就是說,一個操作的作用取決於操作對象的類型。函數的多態性,使得函數可以自動適用於所有類別的對象類型。
如果傳給函數的對象有預期的方法和表達式操作符,那么函數就兼容對象。如果傳遞的對象不支持預期的接口,Python會在 * 表達式運行時檢測到錯誤,並自動拋出一個異常。
這種特性,使得Python代碼不應該關心特定的類型,函數應該為對象編寫接口,而不是數據類型。當然,這種多態的編程模型意味着:必須測試代碼去檢測執行結果是否錯誤,而不是編寫代碼進行類型檢查。
三,變量的作用域
在Python代碼中變量無處不在,命名空間就是保存變量名的地方,變量名能夠訪問(可見)的命名空間叫做作用域。
當在程序中使用變量名時,Python創建、改變或者查找變量都是在命名空間中進行的,變量名被賦值的位置決定了變量名能夠被訪問的范圍。
1,變量的分類
Python中的變量在第一次被賦值時創建,並且必須經過賦值后才能使用。由於沒有變量的聲明,Python把變量名被賦值的地點關聯為(綁定為)一個特定的命名空間。
根據變量的命名空間,把變量大致分為三類:
- 模塊是全局命名空間,其名稱是模塊文件的名稱,位於模塊內的頂層函數名和變量名叫做全局變量。
- 函數是局部命名空間,其名稱是函數的名稱,位於函數內的函數名和變量名叫做本地變量。
- 由於一個函數可以嵌套在其他函數內,這使得函數的定義具有層次。我們把一個不在本函數內定義的、而是在上層函數中定義的本地變量叫做非本地變量,也就是,這個變量不是當前函數的本地變量,而是上層函數的本地變量。
2,變量的作用域
變量的作用域是指變量可見的范圍,一個變量的作用域總是由變量被賦值的地方決定的,也就是說,變量被賦值的地方決定了變量可見的范圍。
- 如果一個變量賦值的地點是在def之內,那么該變量是本地變量,作用域在def之內,在def之外,本地變量是不可見的。
- 如果一個變量賦值的地點是在def之外,那么該變量是全局變量,作用域是全局的,在函數內可以引用全局變量。
在默認情況下,一個函數的所有變量名都是與函數的命名空間相關聯的:
- 一個在def內定義的變量名只能被def的代碼使用,不能在函數的外部調用該變量名;
- def中的變量名與def之外的變量名並不沖突,一個在def之外被賦值的變量x和在def中被賦值的變量x是不同的變量。
由於變量可以在三個地方分配,那么變量的作用域實際上分為三類:
- 如果一個變量是在def之內賦值,變量可見范圍是在函數內,變量的作用域是本地(local);
- 如果一個變量是在def之內賦值,對該函數中嵌套的函數來說,該變量的作用域是非本地的(nonlocal),或稱作嵌套(enclosed)作用域;
- 如果一個變量是在def之外賦值,變量可見的范圍是整個模塊,變量的作用域是全局(global)。
3,作用域法則
所有變量名都可以歸納為內置的(builtin)、全局的(global)和本地的(local)。
內置的模塊是Python預先定義好,可以直接引用的。
模塊定義的是全局作用域,全局作用域的作用范圍僅限於單個模塊(文件),也就是說,在一個文件的頂層的變量名對於這個文件內部的代碼而言是全局的。
在默認情況下,在函數內部,賦值的變量名除非聲明為全局變量或非本地變量之外,都是本地作用域內的。函數還定義了嵌套的作用域,使其內部使用的變量名本地化,以便函數內部使用的變量名不會與函數外的變量名沖突。每次對函數的調用都會創建一個新的本地作用域。如果需要給一個嵌套的def中的變量名賦值,從Python 3.0開始,可以使用 nonlocal語句聲明來做到。
注意:模塊頂層的函數名是全局變量,函數內部的def定義的是局部變量;函數的參數是本地變量;一個函數內部的任何類型的賦值都會把一個變量划定為本地的,這意味着,函數內部的賦值(=)語句,def語句等,定義的都是本地變量。
4,變量名解析(LEGB原則)
變量名的解析遵從LEGB原則,當引用一個變量時,Python按照以下順序依次進行查找:從本地變量中、在任意上層函數的作用域、在全局作用域,最后在內置作用域中查找。
LEGB法則解析變量名的詳細機制:
- 當在函數中引用變量名時,Python依次搜索4個作用域:本地作用域(L),然后是上一層結構中def的本地作用域(E),再然后是全局作用域(G),最后是內置作用域(B),並且再第一處能夠找到該變量名的地方停下來。如果變量名再這次搜索中沒有找到,Python會報錯。
- 當在函數中給一個變量名賦值時(而不是在一個表達式中對其進行引用),Python總是創建或改變本地作用域的變量名,除非它已經在當前函數中聲明為全局變量(global)或者非本地變量(nonlocal)。
把嵌套作用域定義為:在當前的def語句之外,在頂層def語句之內的作用域,嵌套作用域的解析細節:
- 對變量x的引用,首先在當前函數內查找變量名x;之后會向上層的函數中查找變量名x,從內向外依次查找嵌套作用域;之后查找當前的全局作用域(模塊);最后再到內置作用域內查找。而全局聲明將會直接從全局作用域(模塊)進行搜索。
- 對變量x的賦值,如果變量x在函數內部聲明為全局變量(global x),那么賦值會修改全局變量x的值;如果變量x在函數內被聲明為非本地變量(nonlocal x),那么賦值會修改最近的嵌套函數的本地作用域內的變量x的值。
5,在函數內引用全局變量
global不是聲明一個類型,而是聲明命名的命名空間是全局的,也就是說,告訴函數打算聲明一個或多個全局變量名。
對於全局變量名,這里對用法作一個總結:
- 全局變量是位於模塊內部頂層的變量名;
- 如果要在函數內對全局變量進行賦值,那么必須聲明該變量是全局的;
- 全局變量名在函數的內部可以直接引用。
例如,x是全局變量,在函數func中使用global 聲明x是全局變量,對x賦值,就是修改全局變量的值:
x=11 def func(): global x x=12
使用global語句把變量聲明為全局變量,這樣,在函數內部就可以修改全局變量的值,也就是說,global語句允許在def中修改全局變量的值。
global語句包含了關鍵字global,其后跟着一個或多個由逗號分開的變量名,當在函數內被賦值或引用時,所有列出來的變量都被映射到全局變量名。
global x,y,z
6,在函數內引用上層的非本地變量
Pytho 3.0 引入了nonlocal語句,用於在一個函數內聲明一個非本地的變量,該變量定義於一個def語句中,並且位於嵌套作用域的上層。
例如,函數foo1定義了變量var1和var2,要想在函數foo2中改變它們的值,必須在foo2中使用nonlocal語句把它們聲明為非本地變量:
def foo1: var1=1 var2=2 ... def foo2: nonlocal var1,var2,..
nonlocal語句是一個聲明語句,用於把函數內的變量聲明為非本地變量。非本地變量是指不在本函數內定義的,而是在上層函數中定義的本地變量。
nonlocal語句的用法解析:
- nonlocal語句完全忽略當前函數的本地作用域,這意味着,nonlocal語句使得對該語句列出的名稱的查找從上層函數的作用域開始,而不是從語句聲明的本地作用域開始。
- nonlocal語句列出的名稱,必須在一個嵌套的def中提前定義過,否則,Python將會產生一個錯誤,也就是說,nonlocal語句聲明的變量只能是def中定義的本地變量,而不能是模塊的全局變量。
- nonlocal語句允許對非本地變量賦值,修改其值。
- 在內嵌的函數中,可以直接引用非本地變量,不需要使用nonlocal語句聲明。
nonlocal語句提供了一種方式,使得嵌套的函數能夠提供可寫的狀態信息,以便在隨后調用嵌套的函數時,能夠記住這些信息。簡而言之,nonlocal語句的引入使得Python允許修改非本地變量。
四,閉合函數
Python的閉合函數是指一個能夠記住嵌套作用域的變量值的函數,盡管那個作用域已經不存在了。
例如,創建一個閉合函數maker,該函數生成並返回一個嵌套函數action,卻並調用這個內嵌的函數。
def maker(x): def action(y): return x*y return action
調用閉合函數,得到的是生成的內嵌函數的一個引用。當我們調用閉合函數,它會返回內嵌函數的引用;當調用內嵌函數action時,我們發現盡管閉合函數已經返回並退出,但是,內嵌函數記住了閉合函數內部的變量x的值。
f=maker(2)
f(3)
也就是說,閉合函數的本地作用域的信息被保留了下來。為了能夠在內嵌的def中使用變量x的值,Python自動記住了所需要的上層作用域的任意值。
注意:如果lambda或者def在函數中定義,嵌套在一個循環之中,並且嵌套的函數引用了一個上層作用域的變量,該變量被循環所改變,所有在這個循環中產生的函數將會由相同的值——在最后一次循環中完成時被引用變量的值。
>>> def maker(): ... acts=[] ... for i in range(5): ... acts.append(lambda x:i**x) ... return acts ... >>> acts=maker() >>> acts[0](2) 16
因為嵌套作用域中的變量在嵌套的函數被調用時才進行檢查,所以,它們實際上記住的是同樣的值(在最后一次循環迭代中循環變量的值)。
也就是說,只有當調用acts[0](2)時,采取檢查變量i的值,此時變i的值是最后一次迭代的值4。要解決這類問題,必須在函數maker調用時,對i的值進行評估,並保存起來。
>>> def maker(): ... acts=[] ... for i in range(5): ... acts.append(lambda x, i=i : i**x) ... return acts
為了讓這類代碼能夠工作,必須使用默認參數把當前的值傳遞給嵌套作用域的變量。因為默認參數是在嵌套函數創建時評估的(而不是其稍后調用時),所以,每一個函數都記住了自己的變量 i 的值。
參考文檔: