python:装饰器
- 前言
- 閉包
- nonlocal 語句
- 裝飾器
- 裝飾器進階:裝飾器流程詳解
- 裝飾器進階:帶參數(shù)的裝飾器
- 裝飾器進階:類裝飾器
- 裝飾器進階:functools.wraps
- 裝飾器進階:多裝飾器的執(zhí)行順序
- 裝飾器進階:裝飾器應用示例
前言
連裝飾器都弄不明白,別說會python。
閉包
要理解裝飾器,先要理解閉包。閉包并不是什么很高深的概念,簡單說就是函數(shù)嵌套,內(nèi)部函數(shù)調(diào)用外部函數(shù)的變量。內(nèi)部函數(shù)沒有調(diào)用變量不構成閉包。
先來看一段代碼:
在inner函數(shù)里面調(diào)用了outer函數(shù)的局部變量string,打印出來的結果是:
(<cell at 0x00000259ACCBD768: str object at 0x00000259ACD307F0>,) None注意:inner是閉包,outer不是。
通常不是在outer函數(shù)內(nèi)部直接操作inner,而是通過返回inner的地址:
def outer():string = "hello world"def inner():print(string)return innerres = outer() res()上面這段代碼就是閉包的基本結構。
閉包的優(yōu)點就在于,函數(shù)調(diào)用完之后,函數(shù)內(nèi)部的局部變量依然在內(nèi)存中。以上面這段代碼為例,一般我們認為outer函數(shù)執(zhí)行完后,其內(nèi)部變量string將不再可用,然而這里我們發(fā)現(xiàn)outer執(zhí)行完之后調(diào)用res,string輸出了。其原理就是把函數(shù)和內(nèi)部變量打包到一起,而沒有“用完則釋放局部變量”。那為什么要使用閉包呢?
看下面這段代碼:
def outer(x):def inner(y):print("%d+%d=%d"%(x,y,x+y))return innerres = outer(1) res(2) res(3)閉包避免了使用全局變量。
此外,閉包的思想和面向對象編程有相似之處,面向對象編程是將一類對象的屬性封裝起來,閉包是將變量和函數(shù)關聯(lián)起來。
下面是用閉包和類兩種思想實現(xiàn)的同一功能:
nonlocal 語句
在python的函數(shù)內(nèi),可以直接引用外部變量,但不能改寫外部變量。閉包也如此。如果你寫出了這樣的代碼,將會報錯:
def outer():count = 0def inner():count += 1inner()outer()在python 2中可以在函數(shù)內(nèi)使用global語句,但全局變量在任何語言中都不被提倡,因為它很難控制,python 3中引入了nonlocal語句解決了這個問題:
def outer():count = 0def inner():nonlocal countcount += 1inner()outer()Nonlocal 與 global 的區(qū)別在于 nonlocal 語句會去搜尋本地變量與全局變量之間的變量,其會優(yōu)先尋找層級關系與閉包作用域最近的外部變量。
裝飾器
所謂裝飾器,就是給函數(shù)添加功能,起到裝飾的作用,所以裝飾器本身就是函數(shù)。
最簡單的思路想要實現(xiàn)給函數(shù)增加功能,代碼如下:
但是這種實現(xiàn)方式有一個缺點:改變了函數(shù)調(diào)用的方式。從func()變成了add_func()。如果要加功能的函數(shù)有很多的話,修改所有的調(diào)用方式無疑工作量太大了。
那就換個寫法:
def add_func():print("add func")def func(f):print("func")return ffunc = func(add_func) func()這樣寫好像可以不用改變函數(shù)原本的調(diào)用方式,又增加了功能。能解決問題,但是這種寫法避不開前面談到的一個問題:局部變量在函數(shù)調(diào)用后被銷毀的問題。所以用閉包來實現(xiàn)裝飾器簡直不能再合適。代碼如下:
def deco(f):def add_func():f()print("add func")return add_funcdef func():print("func")func = deco(func) func()本來裝飾器寫到這里應該就結束了,但python的語法力求簡潔。
上面這段代碼還可以寫成這個樣子:
這種寫法大大簡化了代碼重構的工作量。不需要修改函數(shù)的調(diào)用方式,只需要在被裝飾函數(shù)的前面加上一個 @decoration_name 即可。對比一下兩段代碼,會發(fā)現(xiàn) @deco 其實就等同于 func = deco(func) 。
裝飾器進階:裝飾器流程詳解
裝飾器在裝飾函數(shù)與被裝飾函數(shù)之間跳來跳去,下面來研究一下具體流程。還是以前面那段代碼為例:
def deco(f): #1.定義deco函數(shù)def add_func(): #4.定義add_func函數(shù)f() #8.執(zhí)行f,即執(zhí)行被裝飾函數(shù)funcprint("add func") #9.執(zhí)行新添加的功能return add_func #5.返回add_func函數(shù)地址 # @deco <==> func=deco(func)知道這一點就好理解了 #3.調(diào)用deco函數(shù) 6.把返回回來的函數(shù)地址賦值給func @deco def func(): #2.定義func函數(shù)print("func")func() #7.使用裝飾器跳到add_func 10.回到這里,繼續(xù)下面的代碼裝飾器進階:帶參數(shù)的裝飾器
import timedef printTime(f):def wrapper(*args,**kwargs):print("++++++++++++++++++++++++++++++")start = time.time()f(*args,**kwargs)stop = time.time()print("%s 用時 %s"%(f.__doc__,stop-start))return wrapper@printTime def func1():"""1加到100"""sum = 0;for i in range(1,10001):sum = sum + i;print("sum:%s"%sum)@printTime def func2(name,age):"""print name and age"""print("name:%s\nage:%d"%(name,age))@printTime def func3(num):"""遞歸斐波拉契數(shù)列"""def fib(num):if num == 1 or num == 2:return 1else:return fib(num-1)+fib(num-2)res = fib(num)print(res)func1() func2("hex",22) func3(20)裝飾器進階:類裝飾器
類也可以作為函數(shù)的裝飾器。類作為裝飾器的時候,要構造一個func屬性,用來作為原函數(shù)的載體。
class beforeSleep:def __init__(self,func):self.func = funcdef __call__(self, *args, **kwargs):print("before sleep,i want to read some books.")self.func()print("after sleep,i want to drink some water.")@beforeSleep def sleep():print("i am sleeping.")sleep()裝飾器進階:functools.wraps
使用裝飾器能很好的復用代碼,但是會丟失原函數(shù)的元信息。
def logged(func):def with_logging(*args, **kwargs):print(func.__name__) # 輸出 'with_logging'print(func.__doc__) # 輸出 Nonereturn func(*args, **kwargs)return with_logging@logged def f(x):"""does some math"""return x + x * xprint(logged(f))wraps本身也是一個裝飾器,它能把原函數(shù)的元信息拷貝到裝飾器里面的 func 函數(shù)中,這使得原函數(shù)的元信息不丟失。
from functools import wraps def logged(func):@wraps(func)def with_logging(*args, **kwargs):print(func.__name__) # 輸出 'f'print(func.__doc__) # 輸出 'does some math'return func(*args, **kwargs)return with_logging@logged def f(x):"""does some math"""return x + x * xprint(logged(f))裝飾器進階:多裝飾器的執(zhí)行順序
@a @b @c def f():pass執(zhí)行順序:f = a(b(c(f)))
裝飾器進階:裝飾器應用示例
某網(wǎng)站有三個頁面
- home 所有人都可以訪問
- user 只有用戶本人可以訪問
- message 只有用戶本人可以訪問
給它增加一個功能:訪問限制
import getpassdef auth(f):def func(*args,**kwargs):username = input("Please input username:").strip()#getpass庫使得密碼輸入不會顯示在終端。PyCharm不支持,需要在命令行使用python運行當前腳本password = getpass.getpass("Please input password:").strip()if username=="hex" and password=="a-123456":#如果賬號密碼正確,則調(diào)用原函數(shù)f(*args,**kwargs)else:print("invalid input")return funcdef home():"""home 4 everyone"""print("home")@auth def user():"""user 4 user"""print("user")@auth def message():"""message 4 user"""print("message")home() user() message()轉載于:https://www.cnblogs.com/hextech/p/10073613.html
總結
以上是生活随笔為你收集整理的python:装饰器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 模板方法及策略设计模式实践
- 下一篇: Python,Pandas,Bokeh