前言
對(duì)python的修飾器的理解一直停留在"使用修飾器把函數(shù)注冊(cè)為事件的處理程序"的層次恨搓,也是一知半解;這樣拖著不是辦法灾前,索性今天好好整理一下關(guān)于python修飾器的概念及用法趴拧。
介紹
裝飾器是一個(gè)很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場(chǎng)景懈费,較為經(jīng)典的有插入日志计露、性能測(cè)試、事務(wù)處理等憎乙。裝飾器是解決這類(lèi)問(wèn)題的絕佳設(shè)計(jì)票罐,有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無(wú)關(guān)的雷同代碼并繼續(xù)重用泞边。概括的講该押,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能。
功能
我們首先從一個(gè)簡(jiǎn)單的例子說(shuō)起阵谚,這個(gè)例子是stackflow上的一個(gè)問(wèn)題蚕礼,如何通過(guò)使用如下的代碼實(shí)現(xiàn)輸出<b><i>Hello</i></b>
:
@makebold
@makeitalic
def say():
return "Hello"
先看一下答案:
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print hello() ## 返回 <b><i>hello world</i></b>
這里的@makebold
和@makeitalic
似乎給Hello加上了一層包裝(or修飾),這就是修飾器最明顯的體現(xiàn)梢什。
從需求談起
初期奠蹬,我寫(xiě)了一個(gè)函數(shù)
def foo():
print 'in foo()'
foo()
為了檢查這個(gè)函數(shù)的復(fù)雜度(在網(wǎng)絡(luò)編程中程序的延時(shí)還是很重要的),需要測(cè)算運(yùn)算時(shí)間嗡午,增加了計(jì)算時(shí)間的功能有了下面的代碼:
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'Time Elapsed:', end - start
foo()
這里只是寫(xiě)了一個(gè)函數(shù)囤躁,如果我想測(cè)量多個(gè)函數(shù)的延時(shí),由于必須知道start與end荔睹,所以必須寫(xiě)在程序的開(kāi)頭與結(jié)尾狸演,難道每一個(gè)程序都這樣復(fù)制粘貼么?固然可行应媚,但是严沥,我們可以通過(guò)設(shè)計(jì)模式中將功能與數(shù)據(jù)部分分離一樣,將這個(gè)測(cè)量時(shí)間的函數(shù)分離出去中姜,就像C++中我們可以將這個(gè)測(cè)量時(shí)間的函數(shù)變?yōu)橐粋€(gè)類(lèi)消玄,通過(guò)調(diào)用這個(gè)類(lèi),賦予不同的函數(shù)來(lái)測(cè)量不同的函數(shù)的運(yùn)行時(shí)長(zhǎng)丢胚。在python中翩瓜,由于函數(shù)實(shí)際上就是對(duì)象,所以可以利用類(lèi)似的方法實(shí)現(xiàn):
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'Time Elapsed:', end - start
timeit(foo)
這里func()就可以指定函數(shù)了携龟,但是如果我不想填這個(gè)函數(shù)或者這個(gè)功能函數(shù)并不能修改成類(lèi)似的形式怎么辦兔跌?我們需要的是最大限度的少改動(dòng):
import time
def foo():
print 'in foo()'
# 定義一個(gè)計(jì)時(shí)器,傳入一個(gè)峡蟋,并返回另一個(gè)附加了計(jì)時(shí)功能的方法
def timeit(func):
# 定義一個(gè)內(nèi)嵌的包裝函數(shù)坟桅,給傳入的函數(shù)加上計(jì)時(shí)功能的包裝
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'Time Elapsed:', end - start
# 將包裝后的函數(shù)返回
return wrapper
foo = timeit(foo) #可以直接寫(xiě)成@timeit + foo定義华望,python的"語(yǔ)法糖"
foo()
在這個(gè)代碼中,timeit(foo)不是直接產(chǎn)生調(diào)用效果仅乓,而是返回一個(gè)與foo參數(shù)列表一致的函數(shù)赖舟,此時(shí)此foo非彼foo!因?yàn)榇藭r(shí)的foo具有了timeit的功效夸楣,簡(jiǎn)單來(lái)說(shuō)就是能夠讓你在裝飾前后執(zhí)行代碼而無(wú)須改變函數(shù)本身內(nèi)容宾抓,裝飾器是一個(gè)函數(shù),而其參數(shù)為另外一個(gè)函數(shù)豫喧。
一個(gè)有趣的"漢堡"讓你了解順序
順序在修飾器還是非常重要的石洗,利用一個(gè)代碼展示一下:
def bread(func) :
def wrapper() :
print "</''' '''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func) :
def wrapper() :
print "#tomatoes#"
func()
print "~salad~"
return wrapper
def sandwich(food="--ham--") :
print food
sandwich()
#輸出 : --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#輸出:
#</''' '''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>
加上語(yǔ)法糖,代碼可以更簡(jiǎn)潔:
def bread(func) :
def wrapper() :
print "</''' '''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func) :
def wrapper() :
print "#tomatoes#"
func()
print "~salad~"
return wrapper
@bread
@ingredients
def sandwich(food="--ham--") :
print food
sandwich()
拓展
內(nèi)置修飾器
內(nèi)置的裝飾器有三個(gè)紧显,分別是staticmethod讲衫、classmethod和property,作用分別是把類(lèi)中定義的實(shí)例方法變成靜態(tài)方法孵班、類(lèi)方法和類(lèi)屬性焦人。
對(duì)有參函數(shù)進(jìn)行修飾
一個(gè)參數(shù)
如果原函數(shù)有參數(shù),那閉包函數(shù)必須保持參數(shù)個(gè)數(shù)一致重父,并且將參數(shù)傳遞給原方法
def w1(fun):
def wrapper(name):
print("this is the wrapper head")
fun(name)
print("this is the wrapper end")
return wrapper
@w1
def hello(name):
print("hello"+name)
hello("world")
# 輸出:
# this is the wrapper head
# helloworld
# this is the wrapper end
多個(gè)參數(shù)測(cè)試:
def w2(fun):
def wrapper(*args,**kwargs):
print("this is the wrapper head")
fun(*args,**kwargs)
print("this is the wrapper end")
return wrapper
@w2
def hello(name,name2):
print("hello"+name+name2)
hello("world","!!!")
#輸出:
# this is the wrapper head
# helloworld!!!
# this is the wrapper end
有返回值的函數(shù)
def w3(fun):
def wrapper():
print("this is the wrapper head")
temp=fun()
print("this is the wrapper end")
return temp #要把值傳回去呀;ㄍ帧!
return wrapper
@w3
def hello():
print("hello")
return "test"
result=hello()
print("After the wrapper,I accept %s" %result)
#輸出:
#this is the wrapper head
#hello
#this is the wrapper end
#After the wrapper,I accept test
有參數(shù)的修飾器
直接上代碼:
def func_args(pre='xiaoqiang'):
def w_test_log(func):
def inner():
print('...記錄日志...visitor is %s' % pre)
func()
return inner
return w_test_log
# 帶有參數(shù)的修飾器能夠起到在運(yùn)行時(shí)房午,有不同的功能
# 先執(zhí)行func_args('wangcai')矿辽,返回w_test_log函數(shù)的引用
# @w_test_log
# 使用@w_test_log對(duì)test_log進(jìn)行修飾
@func_args('wangcai')
def test_log():
print('this is test log')
test_log()
#輸出:
#...記錄日志...visitor is wangcai
# this is test log
通用修飾器
對(duì)每個(gè)類(lèi)型都有一個(gè)修飾器形式,怎么記得下來(lái)郭厌?所以就有了這個(gè)"萬(wàn)能修飾器":
def w_test(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
@w_test
def test():
print('test called')
@w_test
def test1():
print('test1 called')
return 'python'
@w_test
def test2(a):
print('test2 called and value is %d ' % a)
test()
test1()
test2(9)
# 輸出:
#test called
#test1 called
#test2 called and value is 9
類(lèi)修飾器
當(dāng)創(chuàng)建一個(gè)對(duì)象后袋倔,直接去執(zhí)行這個(gè)對(duì)象,那么是會(huì)拋出異常的折柠,因?yàn)樗皇莄allable宾娜,無(wú)法直接執(zhí)行,但進(jìn)行修改后扇售,就可以直接執(zhí)行調(diào)用:
class Test(object):
def __call__(self, *args, **kwargs):
print('call called')
t = Test()
print(t())
# 就可以直接執(zhí)行
直接對(duì)類(lèi)進(jìn)行修飾:
class Test(object):
def __init__(self, func):
print('test init')
print('func name is %s ' % func.__name__)
self.__func = func
def __call__(self, *args, **kwargs):
print('this is wrapper')
self.__func()
@Test
def test():
print('this is test func')
test()
#輸出:
# test init
# func name is test
# this is wrapper
# this is test func
后記
先介紹到這里前塔,大致也對(duì)修飾器有了一定的理解。后面自己會(huì)結(jié)合自己的項(xiàng)目繼續(xù)深入學(xué)習(xí)承冰。