如何避開變量作用域的陷阱

講這個知識點之前,先看一個例子:

x=99
def func1():
	global x
	x=88

def func2():
	global x
	x=77

問題:大家覺得x最後的是值到底是多少:88還是77?先思考一下,最後我來揭曉謎底....

python的變量跟其他語言一樣,分爲全局變量和局部變量,這個概念比較好理解,我們來看看python中是如何實現的:

1.全局變量與局部變量

x=100
def func():
	print ('Inside func: x is {}'.format(x))
func()
print 'x is still:{}'.format(x)

>> # 打印結果
Inside func: x is 100
x is still:100

這個比較好理解x是全局變量,作用域是整個文件,函數內部是可以引用的.接着看下面一個例子:

x=100
def func():
	x=10  #多了這一行
	print ('Changed local x to :{}'.format(x))

func()
print 'x is still:{}'.format(x)

>>># 打印結果
Changed local x to :10
x is still:100 

大家看在函數外部有一個變量x,在函數內部也有一個變量x,這兩個變量雖然名字長的一樣,但是是完全不同的:

函數內部的是本地變量,它的生命週期只在函數內部,出了函數就結束了,

而x在函數外部模塊文件中聲明的(python一個文件也叫一個模塊),是全局變量,不會被函數裏面的局部變量影響,所以最後print的x還是100,

有人要問了,有的時候我需要讓這個全局變量在函數裏面處理,改變它的值,怎麼辦?這個python早就考慮到了,往下看~~

 

2.全局變量聲明:

x=100
def func():
	global x #注意加了一個global 關鍵字,表示x是全局作用域
	print 'x is :{}'.format(x)
	x=10
	print ('Changed local x to :{}'.format(x))

func()
print 'Value of x:{}'.format(x)

>>> # 打印結果
x is :100
Changed local x to :10
Value of x:10

這個函數內部多了一個global關鍵字,結果就差很多:

原因在於x被聲明爲函數內的全局變量,通過global這個語句是自己明確地映射到了模塊的作用域

函數內對x重新賦值x=10,會改變函數外x的值,所以最後print x是10

 

全局變量簡單說就是這3點:

全局變量是位於模塊文件內部的頂層的變量名

全局變量如果是在函數內被改變的話,一定要用global

全局變量名在函數內部不經過聲明也可以被引用

 

3.函數內的變量解析原則

有的書上叫LEGB法則,其實講白了就是下面4個過程,當在函數中使用沒有聲明過的變量時,python的搜索順序是:

先是在函數內部的本地作用域(L)  

然後是在上一層的函數的本地作用域(E)

然後是全局作用域(G)

最後是內置作用域(B)

 

簡單說就是從局部到中央,好比你找一個人,村裏找不到找鄉->鄉里找不到找市裏->市裏找不到找到全國檔案局

 

我們來一一解釋一下這些原則

1).本地函數

在函數內部(def或者lambda)通過任何方式賦值的,而且沒有在該函數內聲明爲全局變量的變量名

 

2).上層函數的本地作用域

python函數是支持嵌套,而且多層嵌套,當你在最裏層的函數找不到這個變量的時候,會往上一層的函數找,一層一層由內往外找,舉個例子

def f1():
	x=100
	def f2():
		print x 
	f2()

print f1()
>>
100
None 

 

#因爲f2()打印了之後沒有return,對沒有return的函數就默認返回None

 

 

我來解釋一下:

def定義了一個f1()函數,裏面又嵌套了一個f2()函數,這個def生成了一個函數並將其賦值給變量名f2

f2是f1的本地作用域內的一個本地變量,可以把f2看做一個臨時函數,僅僅在f1內部執行的過程中存在.

f2函數幹了一件事打印x,當在f2()內部找不到的時候,就通過LEGB法則往上找,f1()裏面找到了x.

 

3).全局(模塊)

在模塊文件的頂層賦值的變量名,或者在該文件中的def生成的名爲全局變量的變量名(函數內global聲明的變量)

 

4).內置的作用域

 

這個很多初學者不明白,啥內置,內置了什麼,其實很簡單,python在運行之前會自動的引用一個內置模塊,叫做__builein__,這是python的一個標準庫模塊,直接import進來,可以用dir(__builein__)看一下,裏面都是預定義的一些變量名

 

是不是看到了很多熟悉的面孔(type,sum.sorted,open),對的那些就是內置的變量名,前面3種方法都找不到了就會去內置作用域這個列表裏面.

 

換句話時候若你本地變量有一個跟內置變量一樣的,就會被本地變量覆蓋 

def hider():
	sum='newsum'
	return sum([1,2,3])

print hider()
>>TypeError: 'str' object is not callable

就是因爲LEGB法則,本地的sum變量把內置作用域的sum變量覆蓋了

 

下面總結一下:

開頭的例子的答案,其實X的值不是88也不是77,而是都有可能,因爲不確定你先調用那個函數,變量的值取決於函數調用的順序,而函數自身是任意順序進行排列的,所以88,77都有可能,看哪個函數最後調用.

 

這會導致很難debug,你必須要跟蹤整個程序的控制流程,這其實就引出了另外一個話題,全局變量有相關性,用全局變量來記憶狀態信息太複雜,最後是通過面向對象的方法,用類進行封裝.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章