1. 作用域
在python中,函數(shù)會(huì)創(chuàng)建一個(gè)新的作用域。python開(kāi)發(fā)者可能會(huì)說(shuō)函數(shù)有自己的命名空間,差不多一個(gè)意思。這意味著在函數(shù)內(nèi)部碰到一個(gè)變量的時(shí)候函數(shù)會(huì)優(yōu)先在自己的命名空間里面去尋找。
python中的作用域分4種情況:
L:local,局部作用域,即函數(shù)中定義的變量;
E:enclosing,嵌套的父級(jí)函數(shù)的局部作用域,即包含此函數(shù)的上級(jí)函數(shù)的局部作用域,但不是全局的;
G:globa,全局變量,就是模塊級(jí)別定義的變量;
B:built-in,系統(tǒng)固定模塊里面的變量,比如int, bytearray等。
搜索變量的優(yōu)先級(jí)順序依次是:作用域局部 > 外層作用域 > 當(dāng)前模塊中的全局 > python內(nèi)置作用域,也就是LEGB。
當(dāng)然,local和enclosing是相對(duì)的,enclosing變量相對(duì)上層來(lái)說(shuō)也是local。
1 2 3 4 5 6 7 8 9 10 11 12 13 | x = int ( 2.9 ) # int built-in g_count = 0 # global def outer(): o_count = 1 # enclosing def inner(): i_count = 2 # local print (o_count) # print(i_count) 找不到 inner() outer() # print(o_count) #找不到 |
在Python中,只有模塊(module),類(class)以及函數(shù)(def、lambda)才會(huì)引入新的作用域,其它的代碼塊(如if、try、for等)是不會(huì)引入新的作用域的。
讓我們寫一個(gè)簡(jiǎn)單的函數(shù)看一下 global 和 local 有什么不同:
1 2 3 4 5 6 7 | >>> a_string = "This is a global variable" >>> def foo(): ... print locals () >>> print globals () {..., 'a_string' : 'This is a global variable' } >>> foo() # 2 {} |
內(nèi)置的函數(shù)globals
返回一個(gè)包含所有python解釋器知道的變量名稱的字典。
在#2我調(diào)用了函數(shù) foo
把函數(shù)內(nèi)部本地作用域
里面的內(nèi)容打印出來(lái)。
我們能夠看到,函數(shù)foo
有自己獨(dú)立的命名空間,雖然暫時(shí)命名空間里面什么都還沒(méi)有。
global關(guān)鍵字
當(dāng)內(nèi)部作用域想修改外部作用域的變量時(shí),就要用到global和nonlocal關(guān)鍵字了,當(dāng)修改的變量是在全局作用域(global作用域)上的,就要使用global先聲明一下,代碼如下:
1 2 3 4 5 6 7 8 9 | count = 10 def outer(): global count print (count) count = 100 print (count) outer() #10 #100 |
nonlocal關(guān)鍵字
global關(guān)鍵字聲明的變量必須在全局作用域上,不能嵌套作用域上,當(dāng)要修改嵌套作用域(enclosing作用域,外層非全局作用域)中的變量怎么辦呢,這時(shí)就需要nonlocal關(guān)鍵字了
1 2 3 4 5 6 7 8 9 10 11 | def outer(): count = 10 def inner(): nonlocal count count = 20 print (count) inner() print (count) outer() #20 #20 |
2. 變量解析規(guī)則
當(dāng)然這并不是說(shuō)我們?cè)诤瘮?shù)里面就不能訪問(wèn)外面的全局變量。
在python的作用域規(guī)則里面,創(chuàng)建變量一定會(huì)一定會(huì)在當(dāng)前作用域里創(chuàng)建一個(gè)變量,
但是訪問(wèn)或者修改變量時(shí)會(huì)先在當(dāng)前作用域查找變量,沒(méi)有找到匹配變量的話會(huì)依次向上在閉合的作用域里面進(jìn)行查看找。
所以如果我們修改函數(shù)foo
的實(shí)現(xiàn)讓它打印全局的作用域里的變量也是可以的:
1 2 3 4 5 | >>> a_string = "This is a global variable" >>> def foo(): ... print a_string # 1 >>> foo() This is a global variable |
在#1處,python解釋器會(huì)嘗試查找變量a_string
,當(dāng)然在函數(shù)的本地作用域
里面是找不到的,所以接著會(huì)去上層的作用域里面去查找。
但是另一方面,假如我們?cè)诤瘮?shù)內(nèi)部給全局變量賦值,結(jié)果卻和我們想的不一樣:
1 2 3 4 5 6 7 8 | >>> a_string = "This is a global variable" >>> def foo(): ... a_string = "test" # 1 ... print locals () >>> foo() { 'a_string' : 'test' } >>> a_string # 2 'This is a global variable' |
我們能夠看到,全局變量能夠被訪問(wèn)到(如果是可變數(shù)據(jù)類型(像list,dict這些)甚至能夠被更改)但是賦值不行。
在函數(shù)內(nèi)部的#1處,我們實(shí)際上新創(chuàng)建
了一個(gè)局部變量,隱藏
全局作用域中的同名變量。
我們可以通過(guò)打印出局部命名空間中的內(nèi)容得出這個(gè)結(jié)論。
我們也能看到在#2處打印出來(lái)的變量a_string
的值并沒(méi)有改變。
3. 變量生存周期
值得注意的一個(gè)點(diǎn)是,變量不僅是生存在一個(gè)個(gè)的命名空間內(nèi),他們都有自己的生存周期,請(qǐng)看下面這個(gè)例子:
1 2 3 4 5 6 7 | >>> def foo(): ... x = 1 >>> foo() >>> print x # 1 Traceback (most recent call last): ... NameError: name 'x' is not defined |
#1處發(fā)生的錯(cuò)誤不僅僅是因?yàn)?code>作用域規(guī)則導(dǎo)致的(盡管這是拋出了NameError的錯(cuò)誤的原因)
它還和python以及其它很多編程語(yǔ)言中函數(shù)調(diào)用實(shí)現(xiàn)的機(jī)制有關(guān)。
在這個(gè)地方這個(gè)執(zhí)行時(shí)間點(diǎn)并沒(méi)有什么有效的語(yǔ)法讓我們能夠獲取變量x
的值,因?yàn)樗@個(gè)時(shí)候壓根不存在!
函數(shù)foo
的命名空間隨著函數(shù)調(diào)用開(kāi)始而開(kāi)始,結(jié)束而銷毀。
4. 嵌套函數(shù)
Python允許創(chuàng)建嵌套函數(shù)。這意味著我們可以在函數(shù)里面定義函數(shù)而且現(xiàn)有的作用域和變量生存周期依舊適用。
1 2 3 4 5 6 7 8 | >>> def outer(): ... x = 1 ... def inner(): ... print x # 1 ... inner() # 2 ... >>> outer() 1 |
python解釋器需找一個(gè)叫x
的本地變量,查找失敗之后會(huì)繼續(xù)在上層的作用域里面尋找。
這個(gè)上層的作用域定義在另外一個(gè)函數(shù)里面。
對(duì)函數(shù)outer
來(lái)說(shuō),變量x
是一個(gè)本地變量,但是如先前提到的一樣,
函數(shù)inner
可以訪問(wèn)封閉的作用域(至少可以讀和修改)。
在#2處,我們調(diào)用函數(shù)inner
,非常重要的一點(diǎn)是:
inner
也僅僅是一個(gè)遵循python變量解析規(guī)則的變量名,python解釋器會(huì)優(yōu)先在outer
的作用域里面對(duì)變量名inner
查找匹配的變量.
5. 閉包
我們先不急著定義什么是閉包,先來(lái)看看一段代碼:
1 2 3 4 5 6 7 8 | >>> def outer(): ... x = 1 ... def inner(): ... print x # 1 ... return inner >>> foo = outer() >>> foo.func_closure (<cell at 0x ...: int object at 0x ...>,) |
inner
作為一個(gè)函數(shù)被outer
返回,保存在一個(gè)變量foo
,并且我們能夠?qū)λM(jìn)行調(diào)用foo()
。
不過(guò)它會(huì)正常的運(yùn)行嗎?我們先來(lái)看看作用域規(guī)則。
所有的東西都在python的作用域規(guī)則下進(jìn)行工作:“x
是函數(shù)outer
里的一個(gè)局部變量。
當(dāng)函數(shù)inner
在#1處打印x
的時(shí)候,python解釋器會(huì)在inner
內(nèi)部查找相應(yīng)的變量,當(dāng)然會(huì)找不到,
所以接著會(huì)到封閉作用域里面查找,并且會(huì)找到匹配。
但是從變量的生存周期來(lái)看,該怎么理解呢?
我們的變量x
是函數(shù)outer
的一個(gè)本地變量,這意味著只有當(dāng)函數(shù)outer
正在運(yùn)行的時(shí)候才會(huì)存在。
根據(jù)我們已知的python運(yùn)行模式,我們沒(méi)法在函數(shù)outer
返回之后繼續(xù)調(diào)用函數(shù)inner
,在函數(shù)inner
被調(diào)用的時(shí)候,變量x
早已不復(fù)存在,可能會(huì)發(fā)生一個(gè)運(yùn)行時(shí)錯(cuò)誤。
萬(wàn)萬(wàn)沒(méi)想到,返回的函數(shù)inner
居然能夠正常工作。
Python支持一個(gè)叫做函數(shù)閉包
的特性,用人話來(lái)講就是:
嵌套定義在非全局作用域
里面的函數(shù)能夠記住它在被定義的時(shí)候它所處的封閉命名空間。
這能夠通過(guò)查看函數(shù)的func_closure
屬性得出結(jié)論,這個(gè)屬性里面包含封閉作用域里面的值
(只會(huì)包含被捕捉到的值,比如x
,如果在outer
里面還定義了其他的值,封閉作用域里面是不會(huì)有的)
記住,每次函數(shù)outer
被調(diào)用的時(shí)候,函數(shù)inner
都會(huì)被重新定義。
閉包用途:
裝飾器需要掌握知識(shí):
函數(shù)知識(shí)
函數(shù)名可以作為參數(shù)輸入
寫代碼要遵循開(kāi)發(fā)封閉原則,雖然在這個(gè)原則是用的面向?qū)ο箝_(kāi)發(fā),但是也適用于函數(shù)式編程,簡(jiǎn)單來(lái)說(shuō),它規(guī)定已經(jīng)實(shí)現(xiàn)的功能代碼不允許被修改,但可以被擴(kuò)展,即:
封閉:已實(shí)現(xiàn)的功能代碼塊
開(kāi)放:對(duì)擴(kuò)展開(kāi)發(fā)
如果需要在 函數(shù)執(zhí)行前 額外執(zhí)行其他功能的話,我們就可以用到裝飾器來(lái)實(shí)現(xiàn)~
1 2 3 4 5 | def index(): print ( "Hello !" ) return True index() |
如果我們需要在函數(shù) f1執(zhí)行前先輸出一句 Start ,函數(shù)執(zhí)行后輸出一句 End ,那么我們可以這樣做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | def outer(func): def inner(): print ( "Start" ) result = func() print ( "End" ) return result return inner @outer # index = outer(index) def index(): print ( "Hello !" ) return True result = index() # Start # Hello ! # End |
當(dāng)寫完這段代碼后(函數(shù)未被執(zhí)行、未被執(zhí)行、未被執(zhí)行),python解釋器就會(huì)從上到下解釋代碼,步驟如下:
1.先把 def outer(func) 函數(shù)加載到內(nèi)存
2.執(zhí)行@outer
執(zhí)行@outer 時(shí) , 先把 index 函數(shù) 加載到內(nèi)存 ! 并且內(nèi)部會(huì)執(zhí)行如下操作:
1.執(zhí)行 outer 函數(shù),將 index 作為參數(shù)傳遞 ( 此時(shí) func = index )
2.將 outer 函數(shù)返回值 ( return inner ),重新賦值給 index (index = inner)
然后執(zhí)行下面的 result = index() => 相當(dāng)于 執(zhí)行了 inner() 函數(shù)!!!
問(wèn)題:如果被裝飾的函數(shù)如果有參數(shù)呢?
問(wèn)題:可以裝飾具有處理n個(gè)參數(shù)的函數(shù)的裝飾器嗎?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | def outer(func): def inner( * args, * * kwargs): print ( "start" ) result = func( * args, * * kwargs) print ( "end" ) return result return inner @outer def index1(a1,): print (a1) return True @outer def index2(a1,a2): print (a1,a2) return True @outer def index3(a1,a2,a3): print (a1,a2,a3) return True index1( 5 ) index2( 5 , 6 ) index3( 5 , 6 , 7 ) |
問(wèn)題:一個(gè)函數(shù)可以被多個(gè)裝飾器裝飾嗎?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | def outer_0(func): def inner( * args, * * kwargs): print ( "0.5" ) result = func( * args, * * kwargs) return result return inner def outer_1(func): def inner( * args, * * kwargs): print ( "123" ) result = func( * args, * * kwargs) print ( "456" ) return result return inner @outer_0 @outer_1 def index(a1,a2): print (a1,a2) return True index( 1 , 2 ) |
1.先把 outer_0 、outer_1 和 index 加載到內(nèi)存
2.執(zhí)行 @outer_0 時(shí), func = @outer_1 和 index 函數(shù)的結(jié)合體
3.然后執(zhí)行 @outer_0 的 inner , 其中包含了 @outer_1 和index => 所以先執(zhí)行 @outer_1
4.當(dāng)執(zhí)行 @outer_1 時(shí), func = index , 并執(zhí)行 inner -> 再執(zhí)行 func 即 index 函數(shù)
(原理同 只有一個(gè)裝飾器一樣 , 可以 把 @outer_1 和 def index 看成一個(gè) 結(jié)成的函數(shù) => 當(dāng)作只有一個(gè) 裝飾器@outer_0)
裝飾器還有更大的靈活性,例如帶參數(shù)的裝飾器:在上面的裝飾器調(diào)用中,比如@show_time,
該裝飾器唯一的參數(shù)就是執(zhí)行業(yè)務(wù)的函數(shù)。
裝飾器的語(yǔ)法允許我們?cè)谡{(diào)用時(shí),提供其它參數(shù),比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。
import timedef time_logger(flag=0): def show_time(func): def wrapper(*args,**kwargs): start_time=time.time() func(*args,**kwargs) end_time=time.time() print('spend %s'%(end_time-start_time)) if flag: print('將這個(gè)操作的時(shí)間記錄到日志中') return wrapper return show_time @time_logger(3)def add(*args,**kwargs): time.sleep(1) sum=0 for i in args: sum+=i print(sum) add(2,7,5)
@time_logger(3) 做了兩件事:
?。?)time_logger(3):得到閉包函數(shù)show_time,里面保存環(huán)境變量flag
?。?)@show_time :add=show_time(add)
上面的time_logger是允許帶參數(shù)的裝飾器。它實(shí)際上是對(duì)原有裝飾器的一個(gè)函數(shù)封裝,并返回一個(gè)裝飾器(一個(gè)含有參數(shù)的閉包函數(shù))。
當(dāng)我們使用@time_logger(3)調(diào)用的時(shí)候,Python能夠發(fā)現(xiàn)這一層的封裝,并把參數(shù)傳遞到裝飾器的環(huán)境中。
再來(lái)看看類裝飾器,相比函數(shù)裝飾器,類裝飾器具有靈活度大、高內(nèi)聚、封裝性等優(yōu)點(diǎn)。
使用類裝飾器還可以依靠類內(nèi)部的__call__方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí),就會(huì)調(diào)用此方法。
import timeclass Foo(object): def __init__(self, func): self._func = func def __call__(self): start_time=time.time() self._func() end_time=time.time() print('spend %s'%(end_time-start_time))@Foo #bar=Foo(bar)def bar(): print ('bar') time.sleep(2)bar() #bar=Foo(bar)()>>>>>>>沒(méi)有嵌套關(guān)系了,直接active Foo的 __call__方法
注意 :
使用裝飾器極大地復(fù)用了代碼,但是他有一個(gè)缺點(diǎn)就是原函數(shù)的元信息不見(jiàn)了,比如函數(shù)的docstring、__name__、參數(shù)列表,:
我們有functools.wraps,wraps本身也是一個(gè)裝飾器,它能把原函數(shù)的元信息拷貝到裝飾器函數(shù)中,這使得裝飾器函數(shù)也有和原函數(shù)一樣的元信息了。
from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @loggeddef cal(x): return x + x * x print(cal.__name__) #cal
聯(lián)系客服