python:装饰器
- 前言
- 閉包
- nonlocal 語句
- 裝飾器
- 裝飾器進階:裝飾器流程詳解
- 裝飾器進階:帶參數的裝飾器
- 裝飾器進階:類裝飾器
- 裝飾器進階:functools.wraps
- 裝飾器進階:多裝飾器的執行順序
- 裝飾器進階:裝飾器應用示例
前言
連裝飾器都弄不明白,別說會python。
閉包
要理解裝飾器,先要理解閉包。閉包并不是什么很高深的概念,簡單說就是函數嵌套,內部函數調用外部函數的變量。內部函數沒有調用變量不構成閉包。
先來看一段代碼:
在inner函數里面調用了outer函數的局部變量string,打印出來的結果是:
(<cell at 0x00000259ACCBD768: str object at 0x00000259ACD307F0>,) None注意:inner是閉包,outer不是。
通常不是在outer函數內部直接操作inner,而是通過返回inner的地址:
def outer():string = "hello world"def inner():print(string)return innerres = outer() res()上面這段代碼就是閉包的基本結構。
閉包的優點就在于,函數調用完之后,函數內部的局部變量依然在內存中。以上面這段代碼為例,一般我們認為outer函數執行完后,其內部變量string將不再可用,然而這里我們發現outer執行完之后調用res,string輸出了。其原理就是把函數和內部變量打包到一起,而沒有“用完則釋放局部變量”。那為什么要使用閉包呢?
看下面這段代碼:
def outer(x):def inner(y):print("%d+%d=%d"%(x,y,x+y))return innerres = outer(1) res(2) res(3)閉包避免了使用全局變量。
此外,閉包的思想和面向對象編程有相似之處,面向對象編程是將一類對象的屬性封裝起來,閉包是將變量和函數關聯起來。
下面是用閉包和類兩種思想實現的同一功能:
nonlocal 語句
在python的函數內,可以直接引用外部變量,但不能改寫外部變量。閉包也如此。如果你寫出了這樣的代碼,將會報錯:
def outer():count = 0def inner():count += 1inner()outer()在python 2中可以在函數內使用global語句,但全局變量在任何語言中都不被提倡,因為它很難控制,python 3中引入了nonlocal語句解決了這個問題:
def outer():count = 0def inner():nonlocal countcount += 1inner()outer()Nonlocal 與 global 的區別在于 nonlocal 語句會去搜尋本地變量與全局變量之間的變量,其會優先尋找層級關系與閉包作用域最近的外部變量。
裝飾器
所謂裝飾器,就是給函數添加功能,起到裝飾的作用,所以裝飾器本身就是函數。
最簡單的思路想要實現給函數增加功能,代碼如下:
但是這種實現方式有一個缺點:改變了函數調用的方式。從func()變成了add_func()。如果要加功能的函數有很多的話,修改所有的調用方式無疑工作量太大了。
那就換個寫法:
def add_func():print("add func")def func(f):print("func")return ffunc = func(add_func) func()這樣寫好像可以不用改變函數原本的調用方式,又增加了功能。能解決問題,但是這種寫法避不開前面談到的一個問題:局部變量在函數調用后被銷毀的問題。所以用閉包來實現裝飾器簡直不能再合適。代碼如下:
def deco(f):def add_func():f()print("add func")return add_funcdef func():print("func")func = deco(func) func()本來裝飾器寫到這里應該就結束了,但python的語法力求簡潔。
上面這段代碼還可以寫成這個樣子:
這種寫法大大簡化了代碼重構的工作量。不需要修改函數的調用方式,只需要在被裝飾函數的前面加上一個 @decoration_name 即可。對比一下兩段代碼,會發現 @deco 其實就等同于 func = deco(func) 。
裝飾器進階:裝飾器流程詳解
裝飾器在裝飾函數與被裝飾函數之間跳來跳去,下面來研究一下具體流程。還是以前面那段代碼為例:
def deco(f): #1.定義deco函數def add_func(): #4.定義add_func函數f() #8.執行f,即執行被裝飾函數funcprint("add func") #9.執行新添加的功能return add_func #5.返回add_func函數地址 # @deco <==> func=deco(func)知道這一點就好理解了 #3.調用deco函數 6.把返回回來的函數地址賦值給func @deco def func(): #2.定義func函數print("func")func() #7.使用裝飾器跳到add_func 10.回到這里,繼續下面的代碼裝飾器進階:帶參數的裝飾器
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):"""遞歸斐波拉契數列"""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)裝飾器進階:類裝飾器
類也可以作為函數的裝飾器。類作為裝飾器的時候,要構造一個func屬性,用來作為原函數的載體。
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
使用裝飾器能很好的復用代碼,但是會丟失原函數的元信息。
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本身也是一個裝飾器,它能把原函數的元信息拷貝到裝飾器里面的 func 函數中,這使得原函數的元信息不丟失。
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))裝飾器進階:多裝飾器的執行順序
@a @b @c def f():pass執行順序:f = a(b(c(f)))
裝飾器進階:裝飾器應用示例
某網站有三個頁面
- 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":#如果賬號密碼正確,則調用原函數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:装饰器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 模板方法及策略设计模式实践
- 下一篇: Android进程优先级和垃圾回收机制