Python中的裝飾器是你進入Python大門的一道坎,不管你跨不跨過去它都在那裡。Python中的裝飾器的概念經常會讓人搞得一頭霧水,所以今天就好好來分析一下python中的裝飾器
1.範圍
在python中,範圍分為兩種:全域範圍和局部範圍。
全域範圍是定義在檔案層級的變數,函數名。而局部範圍,則是定義函數內部。
關於範圍,我要理解兩點:a.在全域不能訪問到局部定義的變數 b.在局部能夠訪問到全域定義的變數,但是不能修改全域定義的變數(當然有方法可以修改)
下面我們來看看下面執行個體:
x = 1def funx(): x = 10 print(x) # 列印出10funx()print(x) # 列印出1
如果局部沒有定義變數x,那麼函數內部會從內往外開始尋找x,如果沒有找到,就會報錯
x = 1def funx(): print(x) # 列印出1funx()print(x) # 列印出1x = 1def funx(): def func1(): print(x) # 列印出1 func1()funx()print(x) # 列印出1
因此,關於範圍的問題,只需要記住兩點就行:全域變數能夠被檔案任何地方引用,但修改只能在全域進行操作;如果局部沒有找到所需的變數,就會往外進行尋找,沒有找到就會報錯。
2.進階函數
我們知道,函數名其實就是指向一段記憶體空間的地址,既然是地址,那麼我們可以利用這種特性來。
a函數名可以作為一個值
def delete(ps): import os filename = ps[-1] delelemetns = ps[1] with open(filename, encoding='utf-8') as f_read,\ open('tmp.txt', 'w', encoding='utf-8') as f_write: for line in iter(f_read.readline, ''): if line != '\n': # 處理非空行 if delelemetns in line: line = line.replace(delelemetns,'') f_write.write(line) os.remove(filename) os.rename('tmp.txt',filename)def add(ps): filename = ps[-1] addelemetns = ps[1] with open(filename, 'a', encoding='utf-8') as fp: fp.write("\n", addelemetns)def modify(ps): import os filename = ps[-1] modify_elemetns = ps[1] with open(filename, encoding='utf-8') as f_read, \ open('tmp.txt', 'w', encoding='utf-8') as f_write: for line in iter(f_read.readline, ''): if line != '\n': # 處理非空行 if modify_elemetns in line: line = line.replace(modify_elemetns, '') f_write.write(line) os.remove(filename) os.rename('tmp.txt', filename)def search(cmd): filename = cmd[-1] pattern = cmd[1] with open(filename, 'r', encoding="utf-8") as f: for line in f: if pattern in line: print(line, end="") else: print("沒有找到")dic_func ={'delete': delete, 'add': add, 'modify': modify, 'search': search}while True: inp = input("請輸入您要進行的操作:").strip() if not inp: continue cmd_1 = inp.split() cmd = cmd_1[0] if cmd in dic_func: dic_func[cmd](cmd_1) else: print("Error")
b.函數名可以作為傳回值
def outer(): def inner(): pass return inners = outer()print(s)######輸出結果為#######<function outer.<locals>.inner at 0x000000D22D8AB8C8>
c..函數名可以作為一個參數
def index(): print("index func")def outer(index): s = index s() outer(index)######輸出結果#########index func
所以滿足上面兩個條件中的一個,都可以稱為進階函數.
3.閉包函數
閉包函數必須滿足兩個條件:1.函數內部定義的函數 2.包含對外部範圍而非全域範圍的引用
下面通過一些執行個體來說明閉包函數:
執行個體一:以下僅僅在函數內部定義了一個函數,但並非閉包函數.
def outer(): def inner(): print("inner func excuted") inner() # 調用執行inner()函數 print("outer func excuted")outer() # 調用執行outer函數####輸出結果為##########inner func excutedouter func excuted
執行個體二:以下在函數內部定義了一個函數,而且還引用了一個外部變數x,那麼這個是閉包函數麼?答案:不是
x = 1def outer(): def inner(): print("x=%s" %x) # 引用了一個非inner函數內部的變數 print("inner func excuted") inner() # 執行inner函數 print("outer func excuted")outer()#####輸出結果########x=1inner func excutedouter func excuted
在回頭來看看對閉包函數的定義,是不是兩條都滿足?聰明的你,一定發現不滿足第二條.對,這裡的變數x,是屬於全域變數,而非外部作用於域的變數。再來看看下面例子:
def outer(): x = 1 def inner(): print("x=%s" %x) print("inner func excuted") inner() print("outer func excuted")outer()#####輸出結果#########x=1inner func excutedouter func excuted
顯然,上面執行個體滿足閉包函數的條件。現在,你應該清楚,作為一個閉包函數,必須得滿足上述的兩個條件,缺一不可。但是,一般情況下,我們都會給閉包函數返回一個值.這裡先不說為什麼.在接下來的內容中,你會看到這個傳回值的用途.
def outer(): x = 1 def inner(): print("x=%s" %x) print("inner func excuted") print("outer func excuted") return inner # 返回內建函式名 outer()
現在我們來抽象的定義一下閉包函數。它是函數和與其相關的引用環境組合而成的實體。在實現深約束時,需要建立一個能顯式表示引用環境的東西,並將它與相關的子程式捆綁在一起,這樣捆綁起成為閉包。在上面執行個體中,我們可以發現,閉包函數,它必須包含自己的函數以及一個外部變數才能真正稱得上是一個閉包函數。如果沒有一個外部變數與其綁定,那麼這個函數不能算得上是閉包函數。
那麼怎麼知道一個閉包函數有多少個外部參考變數呢?看看下面代碼.
def outer(): x = 1 y = 2 def inner(): print("x= %s" %x) print("y= %s" %y) print(inner.__closure__) return innerouter()######輸出結果#######(<cell at 0x000000DF9EA965B8: int object at 0x000000006FC2B440>, <cell at 0x000000DF9EA965E8: int object at 0x000000006FC2B460>)
結果表明,在inner內部,引用了兩個外部局部變數。如果引用的是非局部變數,那麼這裡輸出的為None.
閉包函數的特點:
1.內建範圍 2.延遲計算
那麼閉包函數有什麼作用呢?我們清楚的知道,閉包函數在定義時,一定會綁定一個外部環境。這個整體才能算的上是一個閉包函數,那麼我們可以利用這個綁定特性,來完成某些特殊的功能。
執行個體三:根據傳入的URL,來下載頁面源碼
from urllib.request import urlopendef index(url) def get() return urlopen(url).read() return getpython = index("http://www.python.org") # 返回的是get函數的地址print(python()) # 執行get函數《並且將返回的結果列印出來baidu = index("http://www.baidu.com")print(baidu())
有人可以會說,這個不滿足閉包函數的條件啊!我沒有引用非全域的外部變數啊。其實並非如此,給,我們之前說過,只要在函數內部的變數都屬於函數。那麼我在index(url),這個url也屬於函數內部,只不過我們省略一步而已,所以上面那個函數也是閉包函數。
4.裝飾器
有了以上基礎,對於裝飾器就好理解了.
裝飾器:外部函數傳入被裝飾函數名,內建函式返回裝飾函數名。
特點:1.不修改被裝飾函數的調用方式 2.不修改被裝飾函數的原始碼
a.無參裝飾器
有如下執行個體,我們需要計算一下代碼執行的時間。
import time, randomdef index(): time.sleep(random.randrange(1, 5)) print("welcome to index page")
根據裝飾器的特點,我們不能對index()進行任何修改,而且調用方式也不能變。這時候,我們就可以使用裝飾器來完成如上功能.
import time, randomdef outer(func): # 將index的地址傳遞給func def inner(): start_time = time.time() func() # fun = index 即func儲存了外部index函數的地址 end_time = time.time() print("已耗用時間為%s"%(end_time - start_time)) return inner # 返回inner的地址def index(): time.sleep(random.randrange(1, 5)) print("welcome to index page")index = outer(index) # 這裡返回的是inner的地址,並重新賦值給indexindex()
但是,有些情況,被裝飾的函數需要傳遞參數進去,有些函數又不需要參數,那麼如何來處理這種變參數函數呢?下面來看看有參數裝飾器的使用方式.
b.有參裝飾器
def outer(func): # 將index的地址傳遞給func def inner(*args, **kwargs): start_time = time.time() func(*args, **kwargs) # fun = index 即func儲存了外部index函數的地址 end_time = time.time() print("已耗用時間為%s"%(end_time - start_time)) return inner # 返回inner的地址
下面來說說一些其他情況的執行個體。
如果被裝飾的函數有傳回值
def timmer(func): def wrapper(*args,**kwargs): start_time = time.time() res=func(*args,**kwargs) #res來接收home函數的傳回值 stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return res return wrapperdef home(name): time.sleep(random.randrange(1,3)) print('welecome to %s HOME page' %name) return 123123123123123123123123123123123123123123
這裡補充一點,加入我們要執行被裝飾後的函數,那麼應該是如下調用方式:
home = timmer(home) # 等式右邊返回的是wrapper的記憶體位址,再將其賦值給home,這裡的home不在是原來的的那個函數,而是被裝飾以後的函數了。像home = timmer(home)這樣的寫法,python給我們提供了一個便捷的方式------文法糖@.以後我們再要在被裝飾的函數之前寫上@timmer,它的效果就和home = timmer(home)是一樣的。
如果一個函數被多個裝飾器裝飾,那麼執行順序是怎樣的。
import timeimport randomdef timmer(func): def wrapper(): start_time = time.time() func() stop_time=time.time() print('run time is %s' %(stop_time-start_time)) return wrapperdef auth(func): def deco(): name=input('name: ') password=input('password: ') if name == 'egon' and password == '123': print('login successful') func() #wrapper() else: print('login err') return deco@auth # index = auth(timmer(index)) @timmer # index = timmer(index)def index(): time.sleep(3) print('welecome to index page')index()
實驗結果表明,多個裝飾器裝飾一個函數,其執行順序是從下往上。
關於裝飾器,還有一些進階用法,有興趣的可以自己研究研究。