談談python修飾器
前言
對python的修飾器的理解一直停留在"使用修飾器把函數註冊為事件的處理程序"的層次,也是一知半解;這樣拖著不是辦法,索性今天好好整理一下關於python修飾器的概念及用法。
介紹
裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較為經典的有插入日誌、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
功能
我們首先從一個簡單的例子說起,這個例子是stackflow上的一個問題,如何通過使用如下的代碼實現輸出<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修飾),這就是修飾器最明顯的體現。
從需求談起
初期,我寫了一個函數
def foo():
print 'in foo()'
foo()
為了檢查這個函數的復雜度(在網絡編程中程序的延時還是很重要的),需要測算運算時間,增加了計算時間的功能有了下面的代碼:
import time def foo(): start = time.clock() print 'in foo()' end = time.clock() print 'Time Elapsed:', end - start foo()
這裏只是寫了一個函數,如果我想測量多個函數的延時,由於必須知道start與end,所以必須寫在程序的開頭與結尾,難道每一個程序都這樣復制粘貼麽?固然可行,但是,我們可以通過設計模式中將功能與數據部分分離一樣,將這個測量時間的函數分離出去,就像C++中我們可以將這個測量時間的函數變為一個類,通過調用這個類,賦予不同的函數來測量不同的函數的運行時長。在python中,由於函數實際上就是對象,所以可以利用類似的方法實現:
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()就可以指定函數了,但是如果我不想填這個函數或者這個功能函數並不能修改成類似的形式怎麽辦?我們需要的是最大限度的少改動:
import time
def foo():
print 'in foo()'
# 定義一個計時器,傳入一個,並返回另一個附加了計時功能的方法
def timeit(func):
# 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'Time Elapsed:', end - start
# 將包裝後的函數返回
return wrapper
foo = timeit(foo) #可以直接寫成@timeit + foo定義,python的"語法糖"
foo()
在這個代碼中,timeit(foo)不是直接產生調用效果,而是返回一個與foo參數列表一致的函數,此時此foo非彼foo!因為此時的foo具有了timeit的功效,簡單來說就是能夠讓你在裝飾前後執行代碼而無須改變函數本身內容,裝飾器是一個函數,而其參數為另外一個函數。
一個有趣的"漢堡"讓你了解順序
順序在修飾器還是非常重要的,利用一個代碼展示一下:
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~
#<\______/>
加上語法糖,代碼可以更簡潔:
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()
拓展
內置修飾器
內置的裝飾器有三個,分別是staticmethod、classmethod和property,作用分別是把類中定義的實例方法變成靜態方法、類方法和類屬性。
對有參函數進行修飾
一個參數
如果原函數有參數,那閉包函數必須保持參數個數一致,並且將參數傳遞給原方法
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
多個參數測試:
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
有返回值的函數
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
有參數的修飾器
直接上代碼:
def func_args(pre='xiaoqiang'):
def w_test_log(func):
def inner():
print('...記錄日誌...visitor is %s' % pre)
func()
return inner
return w_test_log
# 帶有參數的修飾器能夠起到在運行時,有不同的功能
# 先執行func_args('wangcai'),返回w_test_log函數的引用
# @w_test_log
# 使用@w_test_log對test_log進行修飾
@func_args('wangcai')
def test_log():
print('this is test log')
test_log()
#輸出:
#...記錄日誌...visitor is wangcai
# this is test log
通用修飾器
對每個類型都有一個修飾器形式,怎麽記得下來?所以就有了這個"萬能修飾器":
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
類修飾器
當創建一個對象後,直接去執行這個對象,那麽是會拋出異常的,因為他不是callable,無法直接執行,但進行修改後,就可以直接執行調用:
class Test(object):
def __call__(self, *args, **kwargs):
print('call called')
t = Test()
print(t())
# 就可以直接執行
直接對類進行修飾:
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
後記
先介紹到這裏,大致也對修飾器有了一定的理解。後面自己會結合自己的項目繼續深入學習。
談談python修飾器