今天又把《流暢的Python》翻了一遍,更進一步的理解了Python的global、nonlocal關鍵字以及閉包和裝飾器。
針對定義在python的全局變量,與之相對的是局部變量(一般定義在函數中),全局變量和局部變量的一個主要區別是 :
在函數內部,全局變量只能被訪問,而不能直接被操作,如果想讓全局變量能緊隨函數操作而變化,則需要在函數內部使用global關鍵字,見下面例子
例1:不直接操作全局變量,全局變量值不發生變化
outer = '全局變量'
def f1():
a = outer + 'ok'
print(a)
f1()
print(outer)
輸出 :
全局變量ok
全局變量
例2 :直接在函數內部操作全局變量,導致報錯
outer = '全局變量'
def f2():
outer = outer + 'ok'
print(outer)
f2()
print(outer)
輸出 :
UnboundLocalError: local variable 'outer' referenced before assignment
例子3 :函數內部可以直接操作全局變量,並且使得全局變量在函數調用緊跟變化(這裡我調用兩次,讓結果呈現明顯)
def f3():
global outer
outer = outer + 'ok'
print(outer)
f3()
print(outer)
f3()
print(outer)
輸出 :
全局變量ok
全局變量ok
全局變量okok
全局變量okok
如果我在定義函數時,在函數內部又定義了一個函數,像下面這樣,
def outer(): outerNum = 10 def inner(): pass
如果我想在內部函數裡面調用外部函數的outerNum,並且讓外部outerNum跟隨內部函數調用變化而變化,那就需要nonlocal關鍵字,下面再看幾個和上面global類似的例子便於理解
例1 :這裡我沒有使用nonlocal,所以outerNum不隨內部調用而變化
def outer():
outerNum = 10
def inner():
innerNum = outerNum + 10
print(innerNum)
inner()
print(outerNum)
outer()
輸出 :
20
10
例2:這裡由於沒有使用nonlocal關鍵字就在內部函數對outerNum進行操作,導致報錯
def outer():
outerNum = 10
def inner():
outerNum = outerNum + 10
print(outerNum)
inner()
print(outerNum)
outer()
輸出 :
UnboundLocalError: local variable 'outerNum' referenced before assignment
例3 : 在內部函數使用nonlocal調用外函數變量,外部函數變量內部函數調用而變化(這裡多調用幾次為了更加明顯)
def outer():
outerNum = 10
def inner():
nonlocal outerNum
outerNum = outerNum + 10
print(outerNum)
inner()
print(outerNum)
inner()
print(outerNum)
inner()
print(outerNum)
outer()
輸出 :
20
20
30
30
40
40
閉包,說白了,樣貌就是嵌套函數,只不過外部函數只能調用內部函數,而內部函數可以調用外部函數的變量(這時候,上述的nonlocal關鍵字就起到了作用),我曾經看過一個Dart語言講師提到的 —— 閉包的優點:
1. 常駐內存 2. 不污染全局
看下面這個例子——一個根據持續添加數值求總體均值的函數 :
def averageNUM(): nums = [] def inner(anum): nums.append(anum) return sum(nums) / len(nums) return inner arg = averageNUM() print(arg(1)) print(arg(2)) print(arg(3))
結果 :
1.0 1.5 2.0
由例子可以看出——由於值變成了常駐內存,從而在函數結束後不會釋放(但是這樣占用內存啊)。
這樣定義閉包的形式看起來很像匿名函數,但是匿名函數每次使用之後都會釋放內存(用完就扔)。
在介紹裝飾器之前先看兩個功能相同的函數 —— 都是求數字4的平方根的函數 :
def useFunc(func): return func(4) def getSqrt(n): return n ** .5 print(useFunc(getSqrt)) # 2.0
def useFunc(func): return func(4) @useFunc def getSqrt(n): return n ** .5 print(getSqrt) # 2.0
方案二即一個簡單的裝飾器 —— 根據方案一就可以大致理解,函數的參數是函數時,可以選擇裝飾器,如果只是一個函數當參數,一個主函數是看不出什麼的;如果對多個函數掉同一個裝飾器,就能體現裝飾器的神奇功能——拓展額外功能、統一便於管理。
def useFunc(func): print('額外功能') return func(4) @useFunc def getSqrt(n): return n ** .5 @useFunc def anotherFunc1(n): return n ** 2 @useFunc def anotherFunc2(n): return n * 2 print(getSqrt) print(anotherFunc1) print(anotherFunc2)
輸出 :
額外功能 額外功能 額外功能 2.0 16 8
OK, 差不多就是我今天的收獲了,bye……