前情提要1. 作用域 在python中,函數會創(chuàng)建一個新的作用域。python開發(fā)者可能會說函數有自己的命名空間,差不多一個意思。這意味著在函數內部碰到一個變量的時候函數會優(yōu)先在自己的命名空間里面去尋找。 python中的作用域分4種情況:
搜索變量的優(yōu)先級順序依次是:作用域局部 > 外層作用域 > 當前模塊中的全局 > python內置作用域,也就是LEGB。 當然,local和enclosing是相對的,enclosing變量相對上層來說也是local。
在Python中,只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如if、try、for等)是不會引入新的作用域的。 讓我們寫一個簡單的函數看一下 global 和 local 有什么不同:
內置的函數 在#2我調用了函數 我們能夠看到,函數 global關鍵字 當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字了,當修改的變量是在全局作用域(global作用域)上的,就要使用global先聲明一下,代碼如下:
nonlocal關鍵字 global關鍵字聲明的變量必須在全局作用域上,不能嵌套作用域上,當要修改嵌套作用域(enclosing作用域,外層非全局作用域)中的變量怎么辦呢,這時就需要nonlocal關鍵字了
2. 變量解析規(guī)則 當然這并不是說我們在函數里面就不能訪問外面的全局變量。 在python的作用域規(guī)則里面,創(chuàng)建變量一定會一定會在當前作用域里創(chuàng)建一個變量, 但是訪問或者修改變量時會先在當前作用域查找變量,沒有找到匹配變量的話會依次向上在閉合的作用域里面進行查看找。 所以如果我們修改函數
在#1處,python解釋器會嘗試查找變量 但是另一方面,假如我們在函數內部給全局變量賦值,結果卻和我們想的不一樣:
我們能夠看到,全局變量能夠被訪問到(如果是可變數據類型(像list,dict這些)甚至能夠被更改)但是賦值不行。 在函數內部的#1處,我們實際上 我們可以通過打印出局部命名空間中的內容得出這個結論。 我們也能看到在#2處打印出來的變量 3. 變量生存周期 值得注意的一個點是,變量不僅是生存在一個個的命名空間內,他們都有自己的生存周期,請看下面這個例子:
#1處發(fā)生的錯誤不僅僅是因為 它還和python以及其它很多編程語言中函數調用實現的機制有關。 在這個地方這個執(zhí)行時間點并沒有什么有效的語法讓我們能夠獲取變量 函數 4. 嵌套函數 Python允許創(chuàng)建嵌套函數。這意味著我們可以在函數里面定義函數而且現有的作用域和變量生存周期依舊適用。
python解釋器需找一個叫 這個上層的作用域定義在另外一個函數里面。 對函數 函數 在#2處,我們調用函數
5. 閉包 我們先不急著定義什么是閉包,先來看看一段代碼:
不過它會正常的運行嗎?我們先來看看作用域規(guī)則。 所有的東西都在python的作用域規(guī)則下進行工作:“ 當函數 所以接著會到封閉作用域里面查找,并且會找到匹配。 但是從變量的生存周期來看,該怎么理解呢? 我們的變量 根據我們已知的python運行模式,我們沒法在函數 萬萬沒想到,返回的函數 Python支持一個叫做 嵌套定義在 這能夠通過查看函數的 (只會包含被捕捉到的值,比如 記住,每次函數 閉包用途:
用途1
用途2
裝飾器需要掌握知識:
寫代碼要遵循開發(fā)封閉原則,雖然在這個原則是用的面向對象開發(fā),但是也適用于函數式編程,簡單來說,它規(guī)定已經實現的功能代碼不允許被修改,但可以被擴展,即:
如果需要在 函數執(zhí)行前 額外執(zhí)行其他功能的話,我們就可以用到裝飾器來實現~
如果我們需要在函數 f1執(zhí)行前先輸出一句 Start ,函數執(zhí)行后輸出一句 End ,那么我們可以這樣做:
當寫完這段代碼后(函數未被執(zhí)行、未被執(zhí)行、未被執(zhí)行),python解釋器就會從上到下解釋代碼,步驟如下: 1.先把 def outer(func) 函數加載到內存 2.執(zhí)行@outer 執(zhí)行@outer 時 , 先把 index 函數 加載到內存 ! 并且內部會執(zhí)行如下操作: 1.執(zhí)行 outer 函數,將 index 作為參數傳遞 ( 此時 func = index ) 2.將 outer 函數返回值 ( return inner ),重新賦值給 index (index = inner) 然后執(zhí)行下面的 result = index() => 相當于 執(zhí)行了 inner() 函數!!!
問題:如果被裝飾的函數如果有參數呢?
一個參數
兩個參數問題:可以裝飾具有處理n個參數的函數的裝飾器嗎?
多層裝飾器問題:一個函數可以被多個裝飾器裝飾嗎?
1.先把 outer_0 、outer_1 和 index 加載到內存 2.執(zhí)行 @outer_0 時, func = @outer_1 和 index 函數的結合體 3.然后執(zhí)行 @outer_0 的 inner , 其中包含了 @outer_1 和index => 所以先執(zhí)行 @outer_1 4.當執(zhí)行 @outer_1 時, func = index , 并執(zhí)行 inner -> 再執(zhí)行 func 即 index 函數 (原理同 只有一個裝飾器一樣 , 可以 把 @outer_1 和 def index 看成一個 結成的函數 => 當作只有一個 裝飾器@outer_0)
帶參數的裝飾器裝飾器還有更大的靈活性,例如帶參數的裝飾器:在上面的裝飾器調用中,比如@show_time, 該裝飾器唯一的參數就是執(zhí)行業(yè)務的函數。 裝飾器的語法允許我們在調用時,提供其它參數,比如@decorator(a)。這樣,就為裝飾器的編寫和使用提供了更大的靈活性。 import time def 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('將這個操作的時間記錄到日志中') 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):得到閉包函數show_time,里面保存環(huán)境變量flag ?。?)@show_time :add=show_time(add) 上面的time_logger是允許帶參數的裝飾器。它實際上是對原有裝飾器的一個函數封裝,并返回一個裝飾器(一個含有參數的閉包函數)。 當我們使用@time_logger(3)調用的時候,Python能夠發(fā)現這一層的封裝,并把參數傳遞到裝飾器的環(huán)境中。
類裝飾器再來看看類裝飾器,相比函數裝飾器,類裝飾器具有靈活度大、高內聚、封裝性等優(yōu)點。 使用類裝飾器還可以依靠類內部的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。 import time class 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)()>>>>>>>沒有嵌套關系了,直接active Foo的 __call__方法 注意 : 使用裝飾器極大地復用了代碼,但是他有一個缺點就是原函數的元信息不見了,比如函數的docstring、__name__、參數列表,: 我們有functools.wraps,wraps本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器函數中,這使得裝飾器函數也有和原函數一樣的元信息了。 from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): return x + x * x print(cal.__name__) #cal
|
|
|