傳遞參數
函數傳遞參數時的一些簡要的關鍵點:
- 參數的傳遞是通過自動將對象賦值給本地變數名來實現的。所有的參數實際上都是通過指標進行傳遞的,作為參數被傳遞的對象從來不自動拷貝。
- 在函數內部的參數名的賦值不會影響調用者。
- 改變函數的可變對象參數的值會對調用者有影響。
實際上,Python的參數傳遞模型和C語言的相當相似:
不可變參數”通過值”進行傳遞。像整數和字串這樣的對象是通過對象引用而不是拷貝進行的,但是因為不論怎麼樣都不可能在原處改變不可變對象,實際的效果就很像建立了一份拷貝。
可變對象是通過”指標”進行傳遞的。這就意味著,可變對象能夠在函數內部進行原處修改。
>>避免可變參數的修改
避免參數的修改有很多種方式:
傳遞參數時,傳遞一個拷貝:
L = [1,2]changer(L[:])
函數內部進行拷貝
def changer(b): b=b[:]
將可變對象轉化為不可變對象
L=[1,2]changer(tuple(L))
>>對參數輸出進行類比
對於參數的傳回值有一個小技巧:因為return能夠返回任意種類的對象,如果這些值封裝進一個元組或其他的集合類型,那麼它也能夠返回多個值。
def multiple(x,y): x = 2 y = [2,4] return x,y #Return new values in a tuple
這段代碼貌似返回了兩個值,其實只有一個:一個包含了2個元素的元組,它的括弧是可以省略的。
特定的參數匹配模型
>>基礎知識
匹配模型的大綱:
- 位置:從左至右進行匹配。
- 關鍵字參數:通過參數名進行匹配。(調用者可以定義哪一個函數接受這個值,通過在調用時使用參數的變數名,使用name=value這種文法。)
- 預設參數:為沒有傳入值的參數定義參數值。
- 可變參數:搜集任意多基於位置或關鍵字的參數。
- 可變參數解包:傳遞任意多的基於位置或關鍵字的參數。
- Keyword-only參數:參數必須按照名稱傳遞。(只存在於Python3.0中)
>>匹配文法
| 文法 |
位置 |
解釋 |
| func(value) |
調用者 |
常規參數:通過位置進行匹配。 |
| func(name=value) |
調用者 |
關鍵字參數:通過變數名匹配。 |
| func(*sequence) |
調用者 |
以name傳遞所有的對象,並作為獨立的基於位置的參數。 |
| func(**dict) |
調用者 |
以name成對的傳遞所有的關鍵字/值,並作為獨立的關鍵字參數。 |
| def func(name) |
函數 |
常規參數:通過位置或變數名進行匹配。 |
| def func(name=value) |
函數 |
預設參數值,如果在調用中傳遞的話。 |
| def func(*name) |
函數 |
匹配並收集(在元組中)所有包含位置的參數。 |
| def func(**name) |
函數 |
匹配並收集(在字典中)所有包含位置的參數。 |
| def func(*args,name) |
函數 |
參數必須在調用中按照關鍵字傳遞。 |
| def func(*,name=value) |
函數 |
參數必須在調用中按照關鍵字傳遞。(Python3.0) |
相應的說明:
在函數的調用中(表中的前4行),簡單的通過變數名位置進行匹配,但是使用name=value的形式告訴Python依照變數名進行匹配,這些叫做關鍵字參數。在調用中使用*sequence或**dict允許我們在一個序列或字典中相應地封裝任意多的位置相關或者關鍵字的對象,並且在將他們傳遞給函數的時候,將它們解包為分開的、單個的參數。
在函數的頭部,一個簡單的變數名時通過位置或變數名進行匹配的(取決於調用者是如何傳遞給它參數的),但是name=value的形式定義了預設的參數值。*name的形式收集了任意的額外不匹配的參數到元組中,並且**name的形式將會手機額外的關鍵字參數到字典中。在Python3.0及其以後的版本中,跟在*name或一個單獨的*之後的、任何正式的或預設的參數名稱,都是keyword-only參數,並且必須在調用時按照關鍵字傳遞。
>>細節
在使用混合的參數模型的時候,Python將會遵循下面有關順序的法則。
在函數調用中,參數必須以此順序出現:任何位置參數(value),後面跟著任何關鍵字參數(name=value)和*sequence形式的組合,後面跟著**dict形式。
在函數頭部,參數必須以此順序出現:任何一般參數(name),緊跟著任何預設參數(name=value),後面是name(在Python3.0中是)形式,後面跟著任何name或name=value keyword-only參數(Python3.0中),後面跟著**name形式。
在調用和函數頭部中,如果出現**arg形式的話,都必須出現在最後。
Python內部是使用以下的步驟來在賦值前進行參數匹配的:
- 通過位置分配非關鍵字參數。
- 通過匹配變數名分配關鍵字參數。
- 其他額外的非關鍵字分配到*name元組中。
- 其他額外的關鍵字參數分配到**name字典中。
- 用預設值分配給在頭部未得到分配的參數。
- 在這之後,Python會進行檢測,確保每個參數只傳入了一個值。如果不是這樣的話,將會發生錯誤。當所有匹配都完成了,Python把傳遞給參數名的對象賦值給它們。
>>關鍵字參數和預設參數的執行個體
如果沒有使用任何特殊的匹配文法,Python預設會通過位置從左至右匹配變數名。
def f(a,b,c): print(a,b,c)f(1,2,3) #Prints 1,2,3
關鍵字參數
關鍵字參數允許通過變數名進行匹配,而不是通過位置。
f(c=3,b=2,a=1) #Prints 1,2,3
預設參數
預設參數允許建立函數可選的參數。如果沒有傳入值的話,在函數運行前,參數就被賦了預設值。
def f(a,b=2,c=3): print(a,b,c)f(1) #Prints 1,2,3f(1,4) #Prints 1,4,3f(1,c=6) #Prints 1,2,6
關鍵字參數和預設參數的混合
def func(spam,eggs,totast=0,ham=0): print((spam,eggs,totast=0,ham=0))func(1,2) #Ouput:(1,2,0,0)func(1,ham=1,eggs=0) #Ouput:(1,0,0,1)func(spam=1,eggs=0) #Ouput:(1,0,0,0)func(toast=1,eggs=2,spam=3) #Ouput:(3,2,1,0)func(1,2,3,4) #Ouput:(1,2,3,4)
>>任意參數的執行個體
最後兩種匹配擴充,*和**,是讓函數支援接收任意數目的參數的。
收集參數
在函數定義中,在元組中收集不匹配的位置參數。
def f(*args):print(args)
當這個函數調用時,Python將所有位置相關的參數收集到一個新的元組中,並將這個元組賦值給變數args。因此它是一個一般的元組對象,能夠進行索引或迭代。
**屬性類別似,但是它只對關鍵字參數有效。將這些關鍵字參數傳遞給一個新的字典,這個字典之後將能夠通過一般的字典工具進行處理。在這種情況下,**允許將關鍵字參數轉化為字典,你能夠在之後使用鍵調用進行步進或字典迭代。
def f(a,*pargs,**kargs):print(a,pargs,kargs)f(1,2,3,x=1,y=2) #Prints:1 (2,3) {'x':2,'y':1}
解包參數
在最新的Python版本中,我們在調用函數時能夠使用*文法。在這種情況下,它與函數定義的意思相反。它會解包參數的集合,而不是建立參數的集合。
def func(a,b,c,d):print(a,b,c,d)args=(1,2)args+=(3,4)func(*args) #Prints 1,2,3,4
相似的,在函數調用時,**會以鍵/值對的形式解包一個字典,使其成為獨立的關鍵字參數。
args={'a':1,'b':2,'c':3}args['d']=4func(**args) #Prints 1,2,3,4
注意:別混淆函數頭部和函數調用時*/**的文法:在頭部,它意味著收集任意多的參數,而在調用時,它解包任意數量的參數。
應用函數通用性
if : action,args=func1,(1,)else: action,args=func2,(1,2,3)...action(*args)
>>Python3.0 Keyword-Only參數
Python3.0把函數頭部的定序通用化了,允許我們指定keyword-only參數——即必須只按照關鍵字傳遞並且不會由一個位置參數來填充的參數。
從文法上講,keyword-only參數編碼為命名的參數,出現在參數列表中的*args之後。所有這些參數都必須在調用中使用關鍵字文法來傳遞。
我們也可以在參數列表中使用一個*字元,來表示一個函數不會接受一個變數長度的參數列表,而是仍然期待跟在*後面的所有參數都作為關鍵字傳遞。
def kwonly(a,*,b,c): print(a,b,c)kwonly(1,c=3,b=2) #Prints:1,2,3kwonly(c=3,b=2,a=1) #Prints:1,2,3kwonly(1,2,3) #Error!
上述代碼中,b和c必須按照關鍵字傳遞,不允許其他額外的位置傳遞。
另外,預設函數仍然對keyword-only參數有效,所以,實際上,帶有預設值的keyword-only參數都是可選的,但是,那些沒有預設值的keyword-only參數真正地變成了函數必需的keyword-only參數。
定序 最後,注意keyword-only參數必須在一個單個星號後指定,而不是兩個星號——命名的參數不能出現在**args任意關鍵字形式的後面,並且一個**不能獨自出現在參數列表中。這兩種做法將產生錯誤。
def kwonly(a,**pargs,b,c) #Error!def kwonly(a,**,b,c) #Error!
這就意味著,在一個函數的頭部,keyword-only參數必須編寫在**args任意關鍵字形式之前,且在*args任意位置形式之後。
實際上,在函數調用中,類似的定序也是成立的:當傳遞keyword-only參數的時候,它們必須出現在一個**args形式之前。keyword-only參數可以編寫在*arg之前或者之後,並且可能包含在**args中:
def f(a,*b,c=6,**d):print(a,b,c,d)f(1,*(2,3),**dict(x=4,y=5)) #Prints:1 (2,3) 6 {'x':4,'y':5}f(1,*(2,3),**dict(x=4,y=5),c=7) #Error!f(1,*(2,3),c=7,**dict(x=4,y=5)) #Prints:1 (2,3) 7 {'x':4,'y':5}f(1,c=7,*(2,3),**dict(x=4,y=5)) #Prints:1 (2,3) 7 {'x':4,'y':5}f(1,*(2,3),**dict(x=4,y=5,c=7)) #Prints:1 (2,3) 7 {'x':4,'y':5}
Python範圍
在一個Python程式只用變數名時,Python建立、改變或尋找變數名都是在所謂的命名空間(一個儲存變數名的地方)中進行的。也就是說,在代碼中變數名被賦值的位置決定了這個變數名能被訪問到的範圍,也即決定了它存在於哪個命名空間中。
除了打包程式之外,函數還為程式增加了一個額外的命名空間層:預設情況下,一個函數所有變數名都是與函數的命名空間相關聯的。這意味著:
一個在def內的定義的變數能夠在def內的代碼使用,不能在函數的外部應用這樣的變數名。
def之中的變數名與def之外的變數名並不衝突,一個在def之外被賦值的變數X與在這個def之中賦值的變數X是完全不同的變數。
>>範圍法則
在開始編寫函數之前,我們編寫的所有代碼都是位於一個模組的頂層(也就是說,並不是嵌套在def之中),所以我們使用的變數名要麼是存在於模組檔案本身,要麼就是Python內建預先定義好的。函數定義本地範圍,而模組定義的全域範圍。這兩個範圍有如下關係:
內嵌的模組是全域範圍 每個模組都是一個全域範圍(也就是說,一個建立於模組檔案頂層的變數的命名空間)。對於模組外部來說,該模組的全域變數就成為了這個模組對象的屬性,但是在這個模組中能夠像簡單的變數一樣使用。
全域範圍的作用範圍僅限於單個檔案 這裡的全域指的是在一個檔案的頂層的變數名僅對於這個檔案內部的代碼而言是全域的。在Python中是沒有基於一個單個的、無所不包的情景檔案的全域範圍的。
每次對函數的調用都建立了一個新的本地範圍
賦值的變數名除非聲明為全域變數或非局部變數,否則均為局部變數
所有的變數名都可以歸納為本地、全域或者內建的
>>變數名解析:LEGB原則
Python的變數名解析機制有時稱為LEGB法則,當在函數中使用未認證的變數名時,Python搜尋4個範圍:
- 本地範圍(L)
- 上一層結構中def或lambda的本地範圍(E)(其實就是函數嵌套的情況)
- 全域範圍(G)
- 最後是內建範圍(B)
Python按順序在上面4個範圍中尋找變數,並且在第一個能夠找到這個變數名的地方停下來,如果在這4個範圍中都沒找到,Python會報錯。
這裡需要強調的是,上面四個範圍是函數中代碼的搜尋過程,也就是說,在函數中能直接使用上一層中的變數!
s=10def times(x,y): x=s return x*ytimes(3,4) #return 40 not 12
>>內建範圍
內建範圍是通過一個名為builtin的標準模組來實現的,但是這個變數名自身並沒有放入內建範圍內,所以必須匯入這個檔案才能夠使用它。在Python3.0中,可以使用以下的代碼來查看到底預定義了哪些變數:
import builtinsdir(builtins)
因此,事實上有兩種方法可以引用一個內建函數:通過LEGB法則帶來的好處,或者手動匯入builtin模組。其中第二種方法在一些複雜的任務裡是很有用的,因為一些局部變數有可能會覆蓋內建的變數或函數。再次強調的是,LEGB法則只使它找到的第一處變數名的地方生效!
global語句
global語句是一個命名空間的聲明,它告訴Python解譯器打算產生一個或多個全域變數,也就是說,存在於整個模組內部範圍(命名空間)的變數名。關於全域變數名:
全域變數是位於模組檔案內部頂層的變數名。
全域變數如果是在函數內部被賦值的話,必須經過聲明。
全域變數名在函數的內部不經過聲明也可以被引用。
global語句包含了關鍵字global,其後跟著一個或多個由逗號分開的變數名。當在函數主題被賦值或引用時,所有列出來的變數名將被映射到整個模組的範圍內。 舉個例子:
X=88def func(): global X X = 99func()print(X) #Prints 99
範圍和嵌套函數
這部分內容是關於LEGB尋找法則中E這一層的,它包括了任意嵌套函數內部的本地範圍。嵌套範圍有時也叫做靜態嵌套範圍。實際上,嵌套是一個文法上嵌套的範圍,它是對應於程式原始碼的物理結構上的嵌套結構。
>>嵌套範圍的細節
對於一個函數來說:
一個引用(X)首先在本地(函數內)範圍尋找變數名X;之後會在代碼的文法上嵌套了的函數中的本地範圍,從內到外尋找;之後尋找當前的全域範圍(模組檔案);最後在內建範圍內(模組builtin)。全域聲明將會直接從全域(模組檔案)範圍進行搜尋。其實就是從引用X的地方開始,一層一層網上搜尋,直到找到的第一個X。
在預設情況下,一個賦值(X=value)建立或修改了變數名X的當前範圍。如果X在函數內部聲明為全域變數,它將會建立或改變變數名X為整個模組的範圍。另一方面,如果X在函數內部聲明為nonlocal,賦值會修改最近的嵌套函數的本地範圍中的名稱X。
>>嵌套範圍舉例
X = 99def f1(): X = 88 def f2(): print(X) f2()f1() #Prints 88:enclosing def local
首先需要說明的是,上面這段代碼是合法的,def是一個簡單的執行語句,可以出現在任意其他語句能夠出現的地方,包括嵌套在另一個def之中。代碼中,f2是在f1中定義的函數,在此情況下,f2是一個臨時函數,僅在f1內部執行的過程中存在(並且只對f1中的代碼可見)。通過LEGB尋找法則,f2內的X自動對應到了f1的X。
值得注意的是,這個嵌套範圍尋找在嵌套的函數已經返回後也是有效。
X = 99def f1(): X = 88 def f2(): print(X) #Remember X in enclosing def scope return f2 #Return f2 but don't call itaction = f1() #Make return functionaction() #Call it now:Prints 88
上述代碼中,不管調用幾次action函數,傳回值都是88,f2記住了f1中嵌套範圍中的X,儘管此時f1已經不處於啟用的狀態。
工廠函數
上述這些行為有時叫做閉合(closure)或者工廠函數——一個能夠記住嵌套範圍的變數值的函數,即使那個範圍也許已經不存在了。通常來說,使用類來選項組資訊時更好的選擇,但是像這樣的工廠函數也提供了一種替代方案。 具體的例子:
def maker(N): def action(X): return X ** N return actionf=maker(2) #Pass 2 to Nf(3) #Pass 3 to X,N remembers 2: 3**2,Return 9f(4) #return 4**2g=maker(3) #g remembers 3,f remembers 2g(3) #return 27f(3) #return 9
從上面代碼中可以看到,f和g函數分別記錄了不同的N值,也就是記錄了不同的狀態,每一次對這個工廠函數進行賦值,都會得到一個狀態資訊的集合,每個函數都有自己的狀態資訊,由maker中的變數N保持。
範圍與帶有迴圈變數的預設參數相比較
在已給出的法則中有一個值得注意的特例:如果lambda或者def在函數中定義,嵌套在一個迴圈之中,並且嵌套的函數引用了一個上層範圍的變數,該變數被迴圈所改變,所有在這個迴圈中產生的函數都將會有相同的值——在最後一次迴圈中完成時被引用變數的值。具體的例子:
def makeActions(): acts=[] for i in range(5): #Tries to remember each i acts.append(lambda x: i ** x) #All remember same last it return acts
儘管是在嘗試建立一個函數列表,使得每個函數擁有不同的狀態值,但是事實上,這個列表中的函數的狀態值都是一樣的,是4。因為嵌套範圍中的變數在嵌套的函數被調用時才進行尋找,所以它們實際上記住的是同樣的值(在最後一次迴圈迭代中迴圈變數的值)。
為了能讓這類代碼能夠工作,必須使用預設參數把當前的值傳遞給嵌套範圍的變數。因為預設參數是在嵌套函數建立時評估的(而不是在其稍後調用時),每一個函數記住了自己的變數i的值。
def makeActions(): acts=[] for i in range(5): #Use default instead acts.append(lambda x,i=i: i ** x) #Remember current i return acts{
nonlocal語句
事實上,在Python3.0中,我們也可以修改嵌套範圍變數,只要我們在一條nonlocal語句中聲明它們。使用這條語句,嵌套的def可以對嵌套函數中的名稱進行讀取和寫入訪問。nonlocal應用於一個嵌套的函數的範圍中的一個名稱,而不是所有def之外的全域模組範圍——它們可能只存在於一個嵌套的函數中,並且不能由一個嵌套的def中第一次賦值建立。
換句話說,nonlocal即允許對嵌套的函數範圍中的名稱變數賦值,並且把這樣的名稱範圍尋找限制在嵌套的def。
>>nonlocal基礎
def func(): nonlocal name1,name2...
這條語句允許一個嵌套函數來修改在一個文法嵌套函數的範圍中定義的一個或多個名稱。在Python 2.X中,當一個函數def嵌套在另一個函數中,嵌套的函數可以引用上一層函數中定義的各種變數,但是不能修改它們。在Python3.0中,在一條nonlocal語句中聲明嵌套的範圍,使得嵌套的函數能夠賦值,並且由此也能夠修改這樣的名稱。
除了允許修改嵌套的def中的名稱,nonlocal語句還加快了引用——就像global語句一樣,nonlocal使得對該語句中列出的名稱的尋找從嵌套的def的範圍中開始,而不是從聲明函數的本地範圍開始,也就是說,nonlocal也意味著”完全略過我的本地範圍”。
實際上,當執行到nonlocal語句的時候,nonlocal中列出的名稱必須在一個嵌套的def中提前定義過,否則,將會產生一個錯誤。直接效果和global很相似:global意味著名稱位於上一層的模組中,nonlocal意味著它們位於一個上一層的def函數中。nonlocal甚至更加嚴格——範圍尋找只限定在嵌套的def。也就是說,nonlocal只能出現在嵌套的def中,而不能在模組的全域範圍中或def之外的內建範圍中。
當在一個函數中使用的時候,global和nonlocal語句都在某種程度上限制了尋找規則:
global使得範圍尋找從嵌套的模組的範圍開始,並且允許對那裡的名稱賦值。如果名稱不存在與該模組中,範圍尋找繼續到內建範圍,但是,對全域名稱的賦值總是在模組範圍中建立或修改它們。
nonlocal限制範圍尋找只是嵌套的def,要求名稱已經存在於那裡,並且允許對它們賦值。範圍尋找不會繼續到全域或內建範圍。
>>nonlocal應用
使用nonlocal進行修改
def tester(start): state = start #each call gets its own state def nested(label): nonlocal state #remember state in enclosing scope print(label,state) state+=1 #Allowed to change it if onolocal return nestedF = tester(0) #Increments state on each callF('spam') #Prints:spam 0F('ham') #Prints:ham 1F('eggs') #Prints:eggs 2
邊界情況
當執行一條nonlocal語句時,nonlocal名稱必須已經在一個嵌套的def範圍中賦值過,否則將會得到一個錯誤。
nonlocal限制範圍尋找僅為嵌套的def,nonlocal不會在嵌套的模組的全域範圍或所有def之外的內建範圍中尋找。