在python中,變數尋找遵循LGB原則,即優先在局部範圍(local scope)中對變數進行尋找,失敗則在全域範圍(global scope)中進行尋找,最後嘗試再內建範圍(build-in scope)內尋找,如果還是未找到的話,則拋出異常。後來由於閉包和嵌套函數的出現,範圍又增加了外部範圍,這樣變數的尋找範圍優先順序變為:局部、外部、全域和內建。 範圍由def、class、lambda等語句產生,if、try、for等語句並不會產生新的範圍。我們看下面的一個例子:
| 代碼如下 |
複製代碼 |
def scope1_f1(): def f2(): print local_v local_v = 'local' f2() print global_v if __name__ == "__main__": global_v = 'global' scope1_f1() |
在scope1_f1函數中我們並未對global_str進行賦值(即scope1_f1的局部範圍中並不存在變數lobal_str),但程式正常輸出結果'local'和'global'。
一 局部範圍中不應對全域變數進行賦值
需要注意的是雖然我們可以在函數中對全域的變數進行訪問,但一旦局部範圍中對全域變數進行了賦值操作,python解譯器就不會從全域範圍中尋找,而會拋出UnboundLocalError錯誤。該規則在由局部範圍向外部範圍尋找時同樣有效。
| 代碼如下 |
複製代碼 |
def scope1_f1(): print global_v global_v = 'local' if __name__ == "__main__": global_v = 'global' scope1_f1() |
運行程式我們會得到:UnboundLocalError: local variable 'global_v' referenced before assignment
有資料將該規則描述為“局部範圍中全域變數應是唯讀”是不準確的。因為如果變數為list等類型,我們可以通過append這樣的方法來修改全域變數,而不影響局部範圍對變數的訪問。
| 代碼如下 |
複製代碼 |
def scope1_f1(): print global_v global_v.add('local') if __name__ == "__main__": global_v = ['global'] scope1_f1() |
二 局部範圍中使用 global實現對全域變數進行賦值
如果說確實需要對全域變數進行賦值的話,應在局部範圍中使用global來修飾變數。python2.6中global對變數的修飾可以在函數的任意地方進行,但如果global在賦值之後的話,解譯器會提示SyntaxWarning 。
| 代碼如下 |
複製代碼 |
def scope1_f1(): global global_v print global_v global_v = 'local' if __name__ == "__main__": global_v = 'global' scope1_f1() |
三 繼承類優先使用第一個基類中的變數
在沒有對變數初始化的情況下,繼承類會優先使用第一個基類中的變數。下面的例子會輸出1而不是2。(說明:如果繼承類為初始化函數,會優先調第一個基類的初始化函數,如果前面的基類都沒有的話才會調後面基類的初始化函數,初始化函數對變數的修改不在本文討論範圍),
| 代碼如下 |
複製代碼 |
class sclass1(): a = 1 def run(self): print self.a class sclass2(): a = 2 def run(self): print self.a class dclass(sclass1, sclass2): def run(self): print self.a if __name__ == "__main__": a = dclass() a.run() |
四 全域範圍指的是本模組而不是程式
在變數尋找時只會在本模組範圍內進行變數的尋找,即使使用from xxx import *也不會垮模組尋找。在python中匯入一個模組可以理解為是將另外一個模組各變數賦值給當前模組的同名變數, 對當前模組中變數的賦值不會影響到匯入模組的變數。
| 代碼如下 |
複製代碼 |
main.py from module2 import * if __name__ == "__main__": module2_v = 5 module2_v1 = 3 print_modul2() modul2.py module2_v1 = 'module2' def print_modul2(): print module2_v1 print module2_v |
在python中一切都是對象和變數,因此對函數、模組等的的尋找同樣遵循上面的規則。
最後還存在的一個問題是:使用global可以再局部範圍中對全域變數賦值,那麼如何在局部範圍中對外部變數賦值呢? 目前除了使用list這樣的引用傳遞外我還沒發現其他解決方案,留在這裡大家一起思考吧。
Python是靜態範圍語言,儘管它自身是一個動態語言。也就是說,在Python中變數的範圍是由它在原始碼中的位置決定的,這與C有些相似,但是Python與C在範圍方面的差異還是非常明顯的。
接下來會談論Python的範圍規則,在這中間也會說明一下Python與C在範圍方面的不同。
在Python 2.0及之前的版本中,Python只支援3種範圍,即局部範圍,全域範圍,內建範圍;在Python 2.2中,Python正式引入了一種新的範圍 --- 嵌套範圍;在Python 2.1中,嵌套範圍可以作為一個選項被開啟;嵌套範圍的引入,本質上為Python實現了對閉包的支援,關於閉包的知識,網上有很多解釋,這裡就不詳細展開了。相應地,變數尋找順序由之前的LGB變成LEGB(L:Local,E:Enclosing,G:Global,B:Built-in)。
在Python中,並不是任何代碼塊都能引入新的範圍,這與C有很大的不同:
| 代碼如下 |
複製代碼 |
#include<stdio.h> int main() { if(2 > 0) { int i = 0; } printf("i = %d", i); return 0; } |
在這段代碼中,if子句引入了一個局部範圍,變數i就存在於這個局部範圍中,但對外不可見,因此,接下來在printf函數中對變數i的引用會引發編譯錯誤。
但是,在Python中卻並非如此:
print i 在這段代碼中,if子句並沒有引入一個局部範圍,變數i仍然處在全域範圍中,因此,變數i對於接下來的print語句是可見的。
實際上,在Python中,只有模組,類以及函數才會引入新的範圍,其它的代碼塊是不會引入新的範圍的。
在Python中,使用一個變數之前不必預先聲明它,但是在真正使用它之前,它必須已經綁定到某個對象;而名字綁定將在當前範圍中引入新的變數,同時屏蔽外層範圍中的同名變數,不論這個名字綁定發生在當前範圍中的哪個位置。
def f():
print i
f() 運行結果將顯示:NameError: global name 'i' is not defined。Python首先在函數f的本地範圍中尋找變數i,尋找失敗,接著在全域範圍和內建範圍中尋找變數i,仍然失敗,最終拋出NameError異常。
| 代碼如下 |
複製代碼 |
i = 0 def f(): i = 8 print i f() print i |
運行結果顯示:8和0。i = 8是一個名字綁定操作,它在函數f的局部範圍中引入了新的變數i,屏蔽了全域變數i,因此f內部的print語句看到的是局部變數i,f外部的print語句看到的是全域變數i。
| 代碼如下 |
複製代碼 |
i = 0 def f(): print i i = 0 f() |
運行結果顯示:UnboundLocalError: local variable 'i' referenced before assignment。在這個例子當中,函數f中的變數i是局部變數,但是在print語句使用它的時候,它還未被綁定到任何對象之上,所以拋出異常。
print i
i = 0 不論是以互動的方式運行,還是以指令檔的方式運行,結果都顯示:NameError: name 'i' is not defined。這裡的輸出結果又與上一個例子不同,這是因為它在頂級範圍(模組範圍)的緣故。對於模組代碼而言,代碼在執行之前,沒有經過什麼預先處理,但是對於函數體而言,代碼在運行之前已經經過了一個預先處理,因此不論名字綁定發生在範圍的那個位置,它都能感知出來。Python雖然是一個靜態範圍語言,但是名字尋找確實動態發生的,因此直到啟動並執行時候,才會發現名字方面的問題。
在Python中,名字綁定在所屬範圍中引入新的變數,同時綁定到一個對象。名字綁定發生在以下幾種情況之下:
1.參數聲明:參數聲明在函數的局部範圍中引入新的變數;
2.賦值操作:對一個變數進行初次賦值會在當前範圍中引入新的變數,後續賦值操作則會重新綁定該變數;
3.類和函數定義:類和函數定義將類名和函數名作為變數引入當前範圍,類體和函數體將形成另外一個範圍;
4.import語句:import語句在當前範圍中引入新的變數,一般是在全域範圍;
5.for語句:for語句在當前範圍中引入新的變數(迴圈變數);
6.except語句:except語句在當前範圍中引入新的變數(異常對象)。
在Python中,類定義所引入的範圍對於成員函數是不可見的,這與C++或者Java是很不同的,因此在Python中,成員函數想要引用類體定義的變數,必須通過self或者類名來引用它。
嵌套範圍的加入,會導致一些代碼編譯不過或者得到不同的運行結果,在這裡Python解譯器會協助你識別這些可能引起問題的地方,給出警告。
locals函數返回所有的局部變數,但是不會返回嵌套範圍中的變數,實際上沒有函數會返回嵌套範圍中的變數。
全域變數(Global variable)
不論是其他普通函數中,還是Class類中,都是可以通過對應的變數名,而可以直接引用的。
局部變數(automatic variable)
對於函數內,包括類Class的函數內,普通的變數,都是自動臨時變數
此處插播背景知識:
變數的優先順序關係
普通局部變數和全域變數的範圍,最核心的區別在於:
內部變數的優先順序大於外部變數
此處即,(函數內的)局部變數,優先順序大於,(外部的)全域變數
換句話說:
A。如果函數內部,出現了和全域變數同名的變數,則說明是局部變數;
B。如果,函數內,沒有同名的局部變數的情況下,對應的變數,則是全域變數。
這個變數的優先順序關係,不僅針對與Python語言,幾乎其他所有語言,都適用,比如C/C++/C#等等。
所以,上面樣本中的__init__中的name,不是全域變數:
name = “whole global name”;
中的name而是,局部變數;
其中,此處是屬於那種,Python(和,或其他特殊)語言所特有的,無需聲明變數,就可以直接使用的情況。
即局部變數name,並沒有聲明,但是就直接拿過來使用,通過:
name = newPersionName;
在初始化,設定為對應的名字,此處即”crifan”了。
類(Class)的變數
類的變數,在類的範圍內,只能通過
ClassName.PropertyName
或者說
ClassName.VariableName
去訪問,當前類ClassName的變數PropertyName
例子中,就是通過
Person.name
去訪問類Person中的變數name的值的。
樣本(Instance)的變數
樣本中的變數,理論上,是用
InstanceObject.PropertyName
去訪問的。
而此處,由於Python中,預設的,約定俗成的寫法,把類的函數的第一個參數的名字,定義為Instance變數,且名字叫做self,所以,就變成了:
self.PropertyName
了。
所以,上述在類的函數中,想要訪問Instance的變數的話,就是通過
self.name,去訪問類Person的樣本self中的變數name了。
什麼是全域變數和局部變數
在函數外,一段代碼最始開所賦值的變數,它可以被多個函數引用,這就是全域變數;
在函數內定義的變數名,只能被函數內部引用,不能在函數外引用這個變數名,這個變數的範圍就是局部的,也叫它為局部變數;
如果函數內的變數名與函數外的變數名相同,也不會發生衝突。好比下面這種情況:
x = 100
def func():
x = 55
x = 100這個指派陳述式所建立的變數X,範圍為全域變數;
x = 55這個指派陳述式所建立的變數X,它的範圍則為局部變數,只能在函數func()內使用。
儘管這兩個變數名是相同的,但它的範圍為它們做了區分。範圍在某種程度上也可以起到防止程式中變數名衝突的作用,但如果做為玩蛇網python初學者來說,盡量避免這種情況發生比較好。
總結
1、變數的範圍由代碼被賦值的位置所決定
2、變數可以在3個不同地方,對應3種不同範圍:
(一)一個變數在函數內賦值,它的作用範圍被定位在函數之內
(二)當變數是在一個嵌套的函數中賦值時,對於這個嵌套的函數來說,這個變數是非本地的
(三)變數在函數外賦值,它範圍就是當前整個檔案的全域變數