Python 装饰器 函数
?
Python裝飾器學(xué)習(xí)(九步入門):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
淺談Python裝飾器:https://blog.csdn.net/mdl13412/article/details/22608283
Python裝飾器與面向切面編程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
一些更加實(shí)用的 Python 裝飾器示例:https://sikasjc.github.io/2018/09/29/somedecorator/
Python裝飾器高級(jí)版—Python類內(nèi)定義裝飾器并傳遞self參數(shù):https://blog.51cto.com/yishi/2354752
?
?
Python 裝飾器學(xué)習(xí)(九步入門)
?
這是在 Python學(xué)習(xí)小組上介紹的內(nèi)容,現(xiàn)學(xué)現(xiàn)賣、多練習(xí)是好的學(xué)習(xí)方式。
?
第一步:最簡(jiǎn)單的函數(shù),準(zhǔn)備附加額外功能
示例代碼:
# -*- coding:gbk -*- '''示例1: 最簡(jiǎn)單的函數(shù),表示調(diào)用了兩次'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return funcdef myfunc():print("? ? ?myfunc() called.")print('*****************************') dec_func = deco(myfunc)print('*****************************') dec_func()""" 結(jié)果: ***************************** before myfunc() called.myfunc() called. after myfunc() called. *****************************myfunc() called. """?
第二步:使用裝飾函數(shù)在函數(shù)執(zhí)行前和執(zhí)行后分別附加額外功能
示例代碼:
# -*- coding:gbk -*- '''示例2: 替換函數(shù)(裝飾) 裝飾函數(shù)的參數(shù)是被裝飾的函數(shù)對(duì)象,返回原函數(shù)對(duì)象 裝飾的實(shí)質(zhì)語句: myfunc = deco(myfunc)'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return funcdef myfunc():print("? ? myfunc() called.")dec_func = deco(myfunc) print('****************************') dec_func() dec_func()""" 結(jié)果: before myfunc() called.myfunc() called. after myfunc() called. ****************************myfunc() called.myfunc() called. """?
第三步:使用語法糖@來裝飾函數(shù)
本例中 deco 沒有使用內(nèi)嵌函數(shù),可以看到第一次執(zhí)行可以進(jìn)入到裝飾函數(shù),但是第二次不會(huì)進(jìn)入裝飾函數(shù)
# -*- coding:gbk -*- '''示例3: 使用語法糖@來裝飾函數(shù),相當(dāng)于“myfunc = deco(myfunc)” 但發(fā)現(xiàn)新函數(shù)只在第一次被調(diào)用,且原函數(shù)多調(diào)用了一次'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return func@deco def myfunc():print("? ? myfunc() called.")# 第一次調(diào)用后,返回的是 deco 里面的 func 對(duì)象, # 所以第二次調(diào)用myfunc() 只輸出 myfunc() called. myfunc()? # 第一次調(diào)用 print('************************') myfunc()? # 第二次調(diào)用""" 結(jié)果: before myfunc() called.myfunc() called. after myfunc() called.myfunc() called. ************************myfunc() called. """?
第四步:使用 內(nèi)嵌包裝函數(shù) 來確保 每次 新函數(shù) 都被調(diào)用(?被裝飾的函數(shù)沒有參數(shù) )
裝飾函數(shù) deco 返回內(nèi)嵌包裝函數(shù)對(duì)象 _deco。使用內(nèi)嵌包裝函數(shù)來確保每次新函數(shù)都被調(diào)用
# -*- coding:gbk -*- '''示例4: 使用內(nèi)嵌包裝函數(shù)來確保每次新函數(shù)都被調(diào)用, 內(nèi)嵌包裝函數(shù)的形參和返回值與原函數(shù)相同,裝飾函數(shù)返回內(nèi)嵌包裝函數(shù)對(duì)象'''def deco(func):def _deco():print("before myfunc() called.")func()print("after myfunc() called.")# 不需要返回func,實(shí)際上應(yīng)返回原函數(shù)的返回值return _deco@deco def myfunc():print("? ? myfunc() called.")return 'ok'myfunc() print('*************************') myfunc()""" 執(zhí)行結(jié)果: before myfunc() called.myfunc() called. after myfunc() called. ************************* before myfunc() called.myfunc() called. after myfunc() called. """示例代碼:無參數(shù)的函數(shù)( 被裝飾的函數(shù)沒有參數(shù) )
# decorator.pyfrom time import ctime, sleepdef time_fun(func):def wrapped_func():print(f"{func.__name__} called at {ctime()}")return func()return wrapped_func@time_fun def foo():passfoo() sleep(2) foo()?
第五步:對(duì)?帶參數(shù)的函數(shù)?進(jìn)行裝飾(?被裝飾的函數(shù)帶參數(shù)? )
示例代碼:
# -*- coding:gbk -*- '''示例5: 對(duì)帶參數(shù)的函數(shù)進(jìn)行裝飾, 內(nèi)嵌包裝函數(shù)的形參和返回值與原函數(shù)相同,裝飾函數(shù)返回內(nèi)嵌包裝函數(shù)對(duì)象'''def deco(func):def _deco(a, b):print("before myfunc() called.")ret = func(a, b)print("after myfunc() called. result: %s" % ret)return retreturn _deco@deco def myfunc(a, b):print(" myfunc(%s,%s) called." % (a, b))return a + bmyfunc(1, 2) print('*************************') myfunc(3, 4)""" 執(zhí)行結(jié)果: before myfunc() called.myfunc(1,2) called. after myfunc() called. result: 3 ************************* before myfunc() called.myfunc(3,4) called. after myfunc() called. result: 7 """示例代碼:被裝飾的函數(shù)帶參數(shù)?
# decorator2.pyfrom time import ctime, sleepdef time_fun(func):def wrapped_func(a, b):print(f"{func.__name__} called at {ctime()}")print(a, b)return func(a, b)return wrapped_func@time_fun def foo(a, b):print(a + b)foo(3, 5) sleep(2) foo(2, 4)?
第六步:對(duì) 參數(shù)數(shù)量不確定 的函數(shù)進(jìn)行裝飾
示例代碼:
# -*- coding:gbk -*- '''示例6: 對(duì)參數(shù)數(shù)量不確定的函數(shù)進(jìn)行裝飾, 參數(shù)用(*args, **kwargs),自動(dòng)適應(yīng)變參和命名參數(shù)'''def deco(func):def _deco(*args, **kwargs):print("before %s called." % func.__name__)ret = func(*args, **kwargs)print("after %s called. result: %s" % (func.__name__, ret))return retreturn _deco@deco def myfunc_1(a, b):print("? ? myfunc_1(%s,%s) called." % (a, b))return a + b@deco def myfunc_2(a, b, c):print("? ? myfunc_2(%s,%s,%s) called." % (a, b, c))return a + b + cprint('*' * 30) myfunc_1(1, 2) print('*' * 30) myfunc_1(3, 4) print('*' * 30) myfunc_2(1, 2, 3) print('*' * 30) myfunc_2(3, 4, 5)""" 執(zhí)行結(jié)果: ****************************** before myfunc_1 called.myfunc_1(1,2) called. after myfunc_1 called. result: 3 ****************************** before myfunc_1 called.myfunc_1(3,4) called. after myfunc_1 called. result: 7 ****************************** before myfunc_2 called.myfunc_2(1,2,3) called. after myfunc_2 called. result: 6 ****************************** before myfunc_2 called.myfunc_2(3,4,5) called. after myfunc_2 called. result: 12 """?
第七步:讓 裝飾器 帶 參數(shù)
示例代碼:
# -*- coding:gbk -*- '''示例7: 在示例4的基礎(chǔ)上,讓裝飾器帶參數(shù), 和上一示例相比在外層多了一層包裝。 裝飾函數(shù)名實(shí)際上應(yīng)更有意義些'''def deco(arg):def _deco(func):def __deco():print("before %s called [%s]." % (func.__name__, arg))func()print("after %s called [%s]." % (func.__name__, arg))return __decoreturn _deco@deco("mymodule") def myfunc():print("? ? myfunc_1() called.")@deco("module2") def myfunc2():print("? ? myfunc_2() called.")print('************************************') myfunc() print('************************************') myfunc2()""" 執(zhí)行結(jié)果: ************************************ before myfunc called [mymodule].myfunc_1() called. after myfunc called [mymodule]. ************************************ before myfunc2 called [module2].myfunc_2() called. after myfunc2 called [module2]. """裝飾器帶參數(shù),在原有裝飾器的基礎(chǔ)上,設(shè)置外部變量
from time import ctime, sleepdef time_fun_arg(pre="hello"):def time_fun(func):def wrapped_func():print("%s called at %s %s"%(func.__name__, ctime(), pre))return func()return wrapped_funcreturn time_fun@time_fun_arg("12345") def foo():pass@time_fun_arg("abcde") def too():passfoo() sleep(2) foo()too() sleep(2) too()?
第八步:讓 裝飾器 帶 類 參數(shù)
示例代碼:
# -*- coding:gbk -*- '''示例8: 裝飾器帶類參數(shù)'''class Locker:def __init__(self):print("locker.__init__() should be not called.")@staticmethoddef acquire():print("locker.acquire() called.(這是靜態(tài)方法)")@staticmethoddef release():print("locker.release() called.(不需要對(duì)象實(shí)例)")def deco(cls):'''cls 必須實(shí)現(xiàn)acquire和release靜態(tài)方法'''def _deco(func):def __deco():print("before %s called [%s]." % (func.__name__, cls))cls.acquire()try:return func()finally:cls.release()return __decoreturn _deco@deco(Locker) def myfunc():print("? ? myfunc() called.")print('*********************************************') myfunc() print('*********************************************') myfunc()""" 執(zhí)行結(jié)果: ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態(tài)方法)myfunc() called. locker.release() called.(不需要對(duì)象實(shí)例) ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態(tài)方法)myfunc() called. locker.release() called.(不需要對(duì)象實(shí)例) """裝飾器 和 閉包 混用:
# coding=utf-8 from time import timedef logged(when):def log(f, *args, **kargs):print("fun:%s args:%r kargs:%r" % (f, args, kargs))# %r字符串的同時(shí),顯示原有對(duì)象類型def pre_logged(f):def wrapper(*args, **kargs):log(f, *args, **kargs)return f(*args, **kargs)return wrapperdef post_logged(f):def wrapper(*args, **kargs):now = time()try:return f(*args, **kargs)finally:log(f, *args, **kargs)print("time delta: %s" % (time() - now))return wrappertry:return {"pre": pre_logged, "post": post_logged}[when]except BaseException as e:print('must be "pre" or "post"')raise e@logged("post") def fun(name):print("Hello, ", name)fun("world!")?
第九步:裝飾器帶類參數(shù),并分拆公共類到其他py文件中,
同時(shí) 演示了對(duì)一個(gè)函數(shù)應(yīng)用多個(gè)裝飾器
mylocker.py
# -*- coding:gbk -*-class MyLocker:def __init__(self):print("mylocker.__init__() called.")@staticmethoddef acquire():print("mylocker.acquire() called.")@staticmethoddef unlock():print("mylocker.unlock() called.")class LockerEx(MyLocker):@staticmethoddef acquire():print("lockerex.acquire() called.")@staticmethoddef unlock():print("lockerex.unlock() called.")def lock_helper(cls):"""cls 必須實(shí)現(xiàn)acquire和release靜態(tài)方法"""def _deco(func):def __deco(*args, **kwargs):print("before %s called." % func.__name__)cls.acquire()try:return func(*args, **kwargs)finally:cls.unlock()return __decoreturn _deco測(cè)試代碼:
# -*- coding:gbk -*-""" 示例 9: 裝飾器帶類參數(shù),并分拆公共類到其他py文件中 同時(shí)演示了對(duì)一個(gè)函數(shù)應(yīng)用多個(gè)裝飾器 """class Example:@lock_helper(MyLocker)def func_1(self):print("\nfunc_1() called.")@lock_helper(MyLocker)@lock_helper(LockerEx)def func_2(self, a, b):print("\nfunc_2() called.")return a + bif __name__ == "__main__":a = Example()a.func_1()print(a.func_1())print(a.func_2(1, 2))print(a.func_2(3, 4))""" 執(zhí)行結(jié)果: before func_1 called. mylocker.acquire() called.func_1() called. mylocker.unlock() called. before func_1 called. mylocker.acquire() called.func_1() called. mylocker.unlock() called. None before __deco called. mylocker.acquire() called. before func_2 called. lockerex.acquire() called.func_2() called. lockerex.unlock() called. mylocker.unlock() called. 3 before __deco called. mylocker.acquire() called. before func_2 called. lockerex.acquire() called.func_2() called. lockerex.unlock() called. mylocker.unlock() called. 7 """?
下面是參考資料,當(dāng)初有不少地方?jīng)]看明白,真正練習(xí)后才明白些:
1. Python裝飾器學(xué)習(xí):http://blog.csdn.net/thy38/article/details/4471421
2. Python裝飾器與面向切面編程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
3. Python裝飾器的理解:http://apps.hi.baidu.com/share/detail/17572338
?
?
Python 裝飾器的 4 種類型
?
- 函數(shù)? 裝飾? 函數(shù)
- 函數(shù)? 裝飾? 類
- 類? 裝飾? 函數(shù)
- 類? 裝飾? 類
?
@wraps(func) 要使用這個(gè)必須導(dǎo)入functools,這個(gè)作用是消除(被裝飾后的函數(shù)名等屬性的改變)副作用
參考:https://blog.csdn.net/freeking101/article/details/109662542
from functools import wrapsdef decorate(src_func):@wraps(src_func)def wrapper():print("hello")src_func()print("world")return wrapper@decorate def func():print("2019-12-31")print(func.__name__, func.__doc__)# 加上 @wraps(src_func) 輸出為func None # 不加 輸出為 wrapper None?
一:函數(shù) 裝飾 函數(shù)
def wrapFun(func):def inner(a, b):print('function name:', func.__name__)r = func(a, b)return rreturn inner@wrapFun def myadd(a, b):return a + bprint(myadd(2, 3))?
裝飾不帶參數(shù)的函數(shù)
# -*- coding: utf-8 -*-def clothes(func):def wear():print('Buy clothes!{}'.format(func.__name__))return func()return wear@clothes def body():print('The body feels cold!')body()#備注:@是語法糖 # 不用語法糖的情況下,使用下面語句也能實(shí)現(xiàn)裝飾作用:把body再加工,再傳給body # body = clothes(body)示例 :
def deco(func):def _deco(*args, **kwargs):print('call deco')func(*args, **kwargs)return _deco@deco def test():print('call test')# 等同于 # def test(): # print('call test') # test = deco(func)?
裝飾帶一個(gè)參數(shù)的函數(shù)
# -*- coding: utf-8 -*-def clothes(func):def wear(anything): # 實(shí)際是定義一個(gè)anything參數(shù),對(duì)應(yīng)body函數(shù)參數(shù)print('Buy clothes!{}'.format(func.__name__))return func(anything) # 執(zhí)行func函數(shù),并傳入調(diào)用傳入的anything參數(shù)# wear = func(anything) # 在這一步,實(shí)際上可以看出來,為啥wear必須帶參數(shù),因?yàn)樗褪莊unc(anything)return wear# 所以clothes的結(jié)果是# clothes = wear = func(anything)# 不用語法糖的情況下就是# body = clothes(body)('hands')# 進(jìn)一步證明:print(body.__name__) 顯示的是wear函數(shù)@clothes def body(part):print('The body feels could!{}'.format(part))body('hands')?
裝飾帶不定長參數(shù)的函數(shù)
通常裝飾器不只裝飾一個(gè)函數(shù),每個(gè)函數(shù)參數(shù)的個(gè)數(shù)也不相同,這個(gè)時(shí)候使用不定長參數(shù)*args,**kwargs
def clothes(func):def wear(*args, **kwargs):print('Buy clothes!{}'.format(func.__name__))return func(*args, **kwargs)return wear@clothes def body(part):print('The body feels could!{}'.format(part))@clothes def head(head_wear, num=2):print('The head need buy {} {}!'.format(num, head_wear))body('hands') head('headdress')?
裝飾器帶參數(shù)
# 把裝飾器再包裝,實(shí)現(xiàn)了seasons傳遞裝飾器參數(shù)。def seasons(season_type):def clothes(func):def wear(*args, **kwargs):if season_type == 1:s = 'spring'elif season_type == 2:s = 'summer'elif season_type == 3:s = 'autumn'elif season_type == 4:s = 'winter'else:print('The args is error!')return func(*args, **kwargs)print('The season is {}!{}'.format(s, func.__name__))return func(*args, **kwargs)return wearreturn clothes@seasons(2) def children():print('i am children')示例:
def deco(*args, **kwargs):def _deco(func):print(args, kwargs)def __deco(*args, **kwargs):print('call deco')func(*args, **kwargs)return __decoreturn _deco@deco('hello', x='ni hao') def test():print('call test')# 等同于 # def test(): # print('call test') # test = deco('hello', x='ni hao')(test)?
二:函數(shù) 裝飾 類
示例:
def deco(*args, **kwargs):def _deco(cls):cls.x = 12return clsreturn _deco@deco('hello') class A(object):pass>>> A.x 12# 等同于 # class A(object): # pass # A = deco('hello')(A)示例:
def wrapper_class(cls):def inner(a):print('class name:', cls.__name__)return cls(a)return inner@wrapper_class class Foo(object):def __init__(self, a):self.a = adef fun(self):print('self.a =', self.a)m = Foo('Are you OK!') m.fun()示例:
?
?
創(chuàng)建單例(Singletons)
單例是一個(gè)只有一個(gè)實(shí)例的類。Python中有幾種常用的單例,包括None、True和False。事實(shí)上,None是一個(gè)單例,允許你使用is關(guān)鍵字比較None。
示例:下面的?@singleton?裝飾器將類的第一個(gè)實(shí)例存儲(chǔ)為屬性,從而將類轉(zhuǎn)換為單例對(duì)象。稍后創(chuàng)建實(shí)例的嘗試只是返回存儲(chǔ)的實(shí)例:
import functoolsdef singleton(cls):"""Make a class a Singleton class (only one instance)"""@functools.wraps(cls)def wrapper_singleton(*args, **kwargs):if not wrapper_singleton.instance:wrapper_singleton.instance = cls(*args, **kwargs)return wrapper_singleton.instancewrapper_singleton.instance = Nonereturn wrapper_singleton@singleton class TheOne:pass如上所示,這個(gè)類裝飾器遵循與我們的函數(shù)裝飾器相同的模板。唯一的區(qū)別是,我們使用cls作為參數(shù)名來表示它是一個(gè)類裝飾器,而不是func。
運(yùn)行效果:
>>> first_one = TheOne() >>> another_one = TheOne()>>> id(first_one) 140094218762280>>> id(another_one) 140094218762280>>> first_one is another_one True很明顯,first_one確實(shí)與another_one完全相同。
在Python中,單例類的使用并不像在其他語言中那樣頻繁。單例的效果通常在模塊中作為全局變量更好地實(shí)現(xiàn)。
?
三:類 裝飾 函數(shù)、方法
?
類 裝飾 函數(shù)
定義一個(gè)類裝飾器,裝飾函數(shù),默認(rèn)調(diào)用 __call__ 方法
class Decorator(object):def __init__(self, func): # 傳送的是test方法self.func = funcdef __call__(self, *args, **kwargs): # 接受任意參數(shù)print('函數(shù)調(diào)用CALL')return self.func(*args, **kwargs) # 適應(yīng)test的任意參數(shù) @Decorator # 如果帶參數(shù),init中的func是此參數(shù)。 def test(hh):print('this is the test of the function !', hh)test('hh')示例:
class ShowFunName(object):def __init__(self, func):self._func = funcdef __call__(self, a):print('function name:', self._func.__name__)return self._func(a)@ShowFunName def bar(a):return aprint(bar('Are you OK'))無參數(shù)
class Deco(object):def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print('call Deco')self.func(*args, **kwargs)@Deco def test():print('call test')# 等同于 test = Deco(test)有參數(shù)
class Deco(object):def __init__(self, *args, **kwargs):print(args, kwargs)def __call__(self, func):def _deco(*args, **kwargs):print('call Deco')func(*args, **kwargs)return _deco@Deco('hello') def test():print('call test')# 等同于 # test = Deco('hello')(func)?
類 裝飾 方法
????定義一個(gè)類裝飾器,裝飾類中的函數(shù),默認(rèn)調(diào)用__get__方法
????實(shí)際上把類方法變成屬性了,還記得類屬性裝飾器吧,@property
????下面自已做一個(gè)property
class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):"""instance:代表實(shí)例,sum中的selfowner:代表類本身,Test類"""print('調(diào)用的是get函數(shù)')return self.func(instance) # instance就是Test類的selfclass Test(object):def __init__(self):self.result = 0@Decoratordef sum(self):print('There is the Func in the Class !')t = Test() print(t.sum) # 眾所周知,屬性是不加括號(hào)的,sum真的變成了屬性示例:
做一個(gè)求和屬性sum,統(tǒng)計(jì)所有輸入的數(shù)字的和
class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):print('調(diào)用的是get函數(shù)')return self.func(instance)class Test(object):def __init__(self, *args, **kwargs):self.value_list = []if args:for i in args:if str(i).isdigit():self.value_list.append(i)if kwargs:for v in kwargs.values():if str(v).isdigit():self.value_list.append(v)@Decoratordef sum(self):result = 0print(self.value_list)for i in self.value_list:result += ireturn resultt = Test(1, 2, 3, 4, 5, 6, 7, 8, i=9, ss=10, strings='lll')print(t.sum)無參數(shù)
class Deco(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):def _deco(*args, **kwargs):print('call Deco')self.func(instance, *args, **kwargs)return _decoclass A(object):@Decodef test(self):print('call test')# 等同于 # class A(object): # def test(self): # print('call test') # test = Deco(test)有參數(shù)
class Deco(object):def __init__(self, *args, **kwargs):print(args, kwargs)def __get__(self, instance, owner):def _deco(*args, **kwargs):print('call Deco')self.func(instance, *args, **kwargs)return _decodef __call__(self, func):self.func = funcreturn selfclass A(object):@Deco('hello')def test(self):print('call test')# 等同于 # class A(object): # def test(self): # print('call test') # test = Deco('hello')(test)?
四:類 裝飾 類
class ShowClassName(object):def __init__(self, cls):self._cls = clsdef __call__(self, a):print('class name:', self._cls.__name__)return self._cls(a)@ShowClassName class Foobar(object):def __init__(self, a):self.value = adef fun(self):print(self.value)a = Foobar('Are you OK') a.fun()?
函數(shù) 裝飾 類中的方法
示例代碼 1:
From:https://www.cnblogs.com/xieqiankun/p/python_decorate_method.html
import time import datetime import requestsdef cost_time(file_name, f_or_m=1):def _deco(origin_func):if 'f' == f_or_m:def wrapper(*args, **kwargs):start_time = datetime.datetime.now()ret_val = origin_func(*args, **kwargs)end_time = datetime.datetime.now()ct = (end_time - start_time).secondsprint(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')return ret_valreturn wrapperelif 'm' == f_or_m:def wrapper(self, *args, **kwargs):start_time = datetime.datetime.now()ret_val = origin_func(self, *args, **kwargs)end_time = datetime.datetime.now()ct = (end_time - start_time).secondsprint(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')return ret_valreturn wrapperreturn _decoclass Test(object):def __init__(self):pass@cost_time(__file__, 'm')def test_1(self):time.sleep(2)pass@cost_time(__file__, 'm')def test_2(self):r = requests.get('http://www.baidu.com')if 200 == r.status_code:print(r.status_code)else:print(r.status_code)if __name__ == '__main__':t = Test()t.test_1()t.test_1()t.test_2()pass運(yùn)行結(jié)果:
[test_1] cost_time:3 [test_1] cost_time:3 200 [test_2] cost_time:0?
示例代碼 2:
From:https://www.kingname.info/2017/04/17/decorate-for-method/
使用 Python 的裝飾器裝飾一個(gè)類的方法,同時(shí)在裝飾器函數(shù)中調(diào)用類里面的其他方法。這里以捕獲一個(gè)方法的異常為例來進(jìn)行說明。
有一個(gè)類Test, 它的結(jié)構(gòu)如下:
class Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restoredef read_value(self):print('here I will do something.')# do something.在類中有一個(gè)方法read_value(),這個(gè)方法在多個(gè)地方被調(diào)用。由于某些原因,方法read_value有可能隨機(jī)拋出Exception導(dǎo)致程序崩潰。所以需要對(duì)整個(gè)方法做try ... except處理。最丑陋的做法如下面的代碼所示:
class Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restoredef read_value(self):try:print('here I will do something.')# do something.except Exception as e:print(f'exception {e} raised, parse exception.')# do other thing.self.revive()這樣寫雖然可以解決問題,但是代碼不Pythonic。
使用裝飾器來解決這個(gè)問題,裝飾器函數(shù)應(yīng)該寫在類里面還是類外面呢?答案是,寫在類外面。那么既然寫在類外面,如何調(diào)用這個(gè)類的其他方法呢?
首先寫出一個(gè)最常見的處理異常的裝飾器:
def catch_exception(origin_func):def wrapper(*args, **kwargs):try:u = origin_func(*args, **kwargs)return uexcept Exception:return 'an Exception raised.'return wrapperclass Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restore@catch_exceptiondef read_value(self):print('here I will do something.')# do something.這種寫法,確實(shí)可以捕獲到?origin_func()的異常,但是如果在發(fā)生異常的時(shí)候,需要調(diào)用類里面的另一個(gè)方法來處理異常,這又應(yīng)該怎么辦?答案是給 wrapper 增加一個(gè)參數(shù):self.
代碼變?yōu)槿缦滦问?#xff1a;
def catch_exception(origin_func):def wrapper(self, *args, **kwargs):try:u = origin_func(self, *args, **kwargs)return uexcept Exception:self.revive() #不用顧慮,直接調(diào)用原來的類的方法return 'an Exception raised.'return wrapperclass Test(object):def __init__(self):passdef revive(self):print('revive from exception.')# do something to restore@catch_exceptiondef read_value(self):print('here I will do something.')# do something.執(zhí)行結(jié)果:
?
示例代碼 3:
import functoolsdef auto_retry(src_func):@functools.wraps(src_func)def wrapper(*args, **kwargs):for i in range(3):try:return src_func(*args, **kwargs)except Exception as e:print(src_func.__name__, e)return wrapperclass TestClass(object):def __init__(self):passdef __del__(self):pass@auto_retrydef crawl(self, url=None):raise Exception('crawl exception')passTestClass().crawl()?
?
類中的方法 裝飾 方法 和 函數(shù)
- 方法:類中的成員函數(shù)叫做 方法
- 函數(shù):不在類中的函數(shù),即普通函數(shù)叫做 函數(shù)
?
類中的方法 裝飾 函數(shù)
在類里面定義個(gè)函數(shù),用來裝飾其它函數(shù),嚴(yán)格意義上說不屬于類裝飾器。
class Buy(object):def __init__(self, func):self.func = func# 在類里定義一個(gè)函數(shù)def clothes(func): # 這里不能用self,因?yàn)榻邮盏氖莃ody函數(shù)# 其它都和普通的函數(shù)裝飾器相同def ware(*args, **kwargs):print('This is a decorator!')return func(*args, **kwargs)return ware@Buy.clothes def body(hh):print('The body feels could!{}'.format(hh))body('hh')?
類中的方法 裝飾 類中的方法
????背景:想要通過裝飾器修改類里的self屬性值。
class Buy(object):def __init__(self):self.reset = True # 定義一個(gè)類屬性,稍后在裝飾器里更改self.func = True# 在類里定義一個(gè)裝飾器def clothes(func): # func接收bodydef ware(self, *args, **kwargs): # self,接收body里的self,也就是類實(shí)例print('This is a decrator!')if self.reset == True: # 判斷類屬性print('Reset is Ture, change Func..')self.func = False # 修改類屬性else:print('reset is False.')return func(self, *args, **kwargs)return ware@clothesdef body(self):print('The body feels could!')b = Buy() # 實(shí)例化類 b.body() # 運(yùn)行body print(b.func) # 查看更改后的self.func值,是False,說明修改完成?
?
?
?
?
前置知識(shí)
?
閉 包
?
javascript 閉包:https://www.runoob.com/js/js-function-closures.html
? ? ? ? 閉包是一種保護(hù)私有變量的機(jī)制,在函數(shù)執(zhí)行時(shí)形成私有的作用域,保護(hù)里面的私有變量不受外界干擾。直觀的說:就是形成一個(gè)不銷毀的棧環(huán)境。
? ? ? ? 閉包在維基百科上的定義如下:?在計(jì)算機(jī)科學(xué)中,閉包(Closure)是詞法閉包(Lexical?Closure)的簡(jiǎn)稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法:認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。??
下面給出一個(gè)使用閉包實(shí)現(xiàn)的?logger factory?的例子:
def logger_factory(prefix="", with_prefix=True):if with_prefix:def logger(msg):print(prefix + msg)return loggerelse:def logger(msg):print(msg)return loggerlogger_with_prefix = logger_factory("Prefix: ") logger_without_prefix = logger_factory(with_prefix=False) logger_with_prefix("msg") logger_without_prefix("msg")運(yùn)行結(jié)果:
Prefix: msg msg在上面這個(gè)閉包的例子中,prefix?變量時(shí)所謂的自由變量,其在?return logger?執(zhí)行完畢后,便脫離了創(chuàng)建它的環(huán)境logger_factory,但因?yàn)槠浔?strong>logger_factory?中定義的?logger?函數(shù)所引用,其生命周期將至少和?logger?函數(shù)相同。這樣,在?logger?中就可以引用到logger_factory?作用域內(nèi)的變量?prefix。
將 閉包 與 namespace 結(jié)合起來 示例:
var = "var in global"def fun_outer():var = "var in fun_outer"unused_var = "this var is not used in fun_inner"print("fun_outer: " + var)print("fun_outer: " + str(locals())) print("fun_outer: " + str(id(var)))def fun_inner():print("fun_inner: " + var)print("fun_inner: " + str(locals()))print("fun_inner: " + str(id(var)))return fun_innerfun_outer()()運(yùn)行結(jié)果如下:
fun_outer: var in fun_outer fun_outer: {'unused_var': 'this var is not used in fun_inner', 'var': 'var in fun_outer'} fun_outer: 2543733314784 fun_inner: var in fun_outer fun_inner: {'var': 'var in fun_outer'} fun_inner: 2543733314784在這個(gè)例子中,當(dāng)?fun_outer?被定義時(shí),其內(nèi)部的定義的?fun_inner?函數(shù)對(duì)?print "fun_inner: " + var?中所引用的?var?變量進(jìn)行搜索,發(fā)現(xiàn)第一個(gè)被搜索到的?var?定義在?fun_outer?的?local namespace?中,因此使用此定義,通過?print "fun_outer: " + str(id(var))?和?print "fun_inner: " + str(id(var)),當(dāng)var?超出?fun_outer?的作用域后,依然存活,而?fun_outer?中的unused_var?變量由于沒有被?fun_inner?所引用,因此會(huì)被?GC。
?
?
什么是閉包?
?
內(nèi)部函數(shù)對(duì)外部函數(shù)作用域里變量的引用(非全局變量),則稱內(nèi)部函數(shù)為閉包。
簡(jiǎn)單說,閉包就是根據(jù)不同的配置信息得到不同的結(jié)果
再來看看專業(yè)的解釋:閉包(Closure)是詞法閉包(Lexical Closure)的簡(jiǎn)稱,是引用了自由變量的函數(shù)。這個(gè)被引用的自由變量將和這個(gè)函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。
?
Python 閉包示例
示例 1:
def make_adder(arg_1=None):def adder(arg_2=None):return arg_1 + arg_2return adderp = make_adder(23) q = make_adder(44)print(p(100)) print(q(100))# 運(yùn)行結(jié)果: # 123 # 144分析一下:
我們發(fā)現(xiàn)?make_adder?是一個(gè)函數(shù),包括一個(gè)參數(shù)?arg_1 ,比較特殊的地方是這個(gè)函數(shù)里面又定義了一個(gè)新函數(shù),這個(gè)新函數(shù)里面的一個(gè)變量正好是外部?make_adder?的參數(shù)。也就是說,外部傳遞過來的?arg_1 參數(shù)已經(jīng)和?adder?函數(shù)綁定到一起了,形成了一個(gè)新函數(shù),我們可以把??arg_1?看做新函數(shù)的一個(gè)配置信息,配置信息不同,函數(shù)的功能就不一樣了,也就是能得到定制之后的函數(shù).
再看看運(yùn)行結(jié)果,我們發(fā)現(xiàn),雖然 p 和 q 都是?make_adder?生成的,但是因?yàn)榕渲脜?shù)不同,后面再執(zhí)行相同參數(shù)的函數(shù)后得到了不同的結(jié)果。這就是閉包。
?
示例 2:
def hello_counter(name):count = [0]def counter():count[0] += 1print('Hello,', name, ',', str(count[0]) + ' access!')return counterhello = hello_counter('king') hello() hello() hello()# 執(zhí)行結(jié)果 # Hello, king , 1 access! # Hello, king , 2 access! # Hello, king , 3 access!分析一下
這個(gè)程序比較有趣,我們可以把這個(gè)程序看做統(tǒng)計(jì)一個(gè)函數(shù)調(diào)用次數(shù)的函數(shù)。count[0]可以看做一個(gè)計(jì)數(shù)器,每執(zhí)行一次?hello?函數(shù),count[0]?的值就加 1。也許你會(huì)有疑問:為什么不直接寫?count?而用一個(gè)列表? 這是 python2 的一個(gè)bug,如果不用列表的話,會(huì)報(bào)這樣一個(gè)錯(cuò)誤:UnboundLocalError: local variable 'count' referenced before assignment.
什么意思? 就是說?conut?這個(gè)變量你沒有定義就直接引用了,我不知道這是個(gè)什么東西,程序就崩潰了。于是在 python3 里面引入了一個(gè)關(guān)鍵字:nonlocal,這個(gè)關(guān)鍵字是干什么的? 就是告訴 python 程序,我的這個(gè)?count?變量是在外部定義的,你去外面找吧。然后 python 就去外層函數(shù)找,然后就找到了?count=0?這個(gè)定義和賦值,程序就能正常執(zhí)行了。
?
示例 2 改進(jìn):
def hello_counter(name):count = 0def counter():nonlocal countcount += 1print('Hello,', name, ',', str(count) + ' access!')return counterhello = hello_counter('king') hello() hello() hello()關(guān)于這個(gè)問題的研究您可以參考:http://linluxiang.iteye.com/blog/789946
?
示例 3:
def make_bold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef make_italic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@make_bold @make_italic def hello():return "hello world"print(hello())# 執(zhí)行結(jié)果 # <b><i>hello world</i></b>簡(jiǎn)單分析
怎么樣? 這個(gè)程序熟悉嗎? 這不是傳說的的裝飾器嗎? 對(duì),這就是裝飾器,其實(shí),裝飾器就是一種閉包,
我們?cè)倩叵胍幌?strong>裝飾器的概念:對(duì)函數(shù)(參數(shù),返回值 等)進(jìn)行加工處理,生成一個(gè)功能增強(qiáng)版的一個(gè)函數(shù)。
再看看閉包的概念,這個(gè)增強(qiáng)版的函數(shù)不就是我們配置之后的函數(shù)嗎 ?
區(qū)別在于,裝飾器的參數(shù)是一個(gè) 函數(shù) 或 類 ,專門對(duì) 類 或 函數(shù) 進(jìn)行加工處理。
Python 里面的好多高級(jí)功能,比如裝飾器,生成器,列表推到,閉包,匿名函數(shù)等。
?
?
?
探索 裝飾器
定義
?
基本語法
語法糖
@bar def foo():print("foo")其等價(jià)于:
def foo(): print("foo") foo = bar(foo)?
無參數(shù) 裝飾器
def foo(func):print('decorator foo')return func@foo def bar():print('bar')bar()foo 函數(shù)被用作裝飾器,其本身接收一個(gè)函數(shù)對(duì)象作為參數(shù),然后做一些工作后,返回接收的參數(shù),供外界調(diào)用。
注意:時(shí)刻牢記?@foo?只是一個(gè)語法糖,其本質(zhì)是?foo = bar(foo)
?
帶參數(shù) 的 裝飾器
示例代碼:
import timedef function_performance_statistics(trace_this=True):if trace_this:def performance_statistics_delegate(func):def counter(*args, **kwargs):start = time.perf_counter()func(*args, **kwargs)end = time.perf_counter()print('used time: %d' % (end - start,))return counterelse:def performance_statistics_delegate(func):return funcreturn performance_statistics_delegate@function_performance_statistics(True) def add(x, y):time.sleep(3)print('add result: %d' % (x + y,))@function_performance_statistics(False) def mul(x, y=1):print('mul result: %d' % (x * y,))add(1, 1) mul(10)上述代碼想要實(shí)現(xiàn)一個(gè)性能分析器,并接收一個(gè)參數(shù),來控制性能分析器是否生效,其運(yùn)行效果如下所示:
add result: 2 used time: 3 mul result: 10上述代碼中裝飾器的調(diào)用等價(jià)于:
import timedef function_performance_statistics(trace_this=True):if trace_this:def performance_statistics_delegate(func):def counter(*args, **kwargs):start = time.perf_counter()func(*args, **kwargs)end = time.perf_counter()print('used time: %d' % (end - start,))return counterelse:def performance_statistics_delegate(func):return funcreturn performance_statistics_delegate@function_performance_statistics(True) def add(x, y):time.sleep(3)print('add result: %d' % (x + y,))@function_performance_statistics(False) def mul(x, y=1):print('mul result: %d' % (x * y,))add = function_performance_statistics(True)(add(1, 1)) mul = function_performance_statistics(False)(mul(10))?
?
類 的 裝飾器
簡(jiǎn)單示例:
def bar(dummy):print('bar')def inject(cls):cls.bar = barreturn cls@inject class Foo(object):passfoo = Foo() foo.bar()上述代碼的?inject?裝飾器為類動(dòng)態(tài)的添加一個(gè)?bar?方法,因?yàn)轭愒谡{(diào)用非靜態(tài)方法的時(shí)候會(huì)傳進(jìn)一個(gè)self?指針,因此?bar?的第一個(gè)參數(shù)我們簡(jiǎn)單的忽略即可,
運(yùn)行結(jié)果如下:bar
?
?
類 裝飾器
類裝飾器 相比 函數(shù)裝飾器,具有靈活度大,高內(nèi)聚、封裝性等優(yōu)點(diǎn)。其實(shí)現(xiàn)起來主要是靠類內(nèi)部的?__call__?方法,當(dāng)使用?@?形式將裝飾器附加到函數(shù)上時(shí),就會(huì)調(diào)用此方法,下面時(shí)一個(gè)實(shí)例:
class Foo(object):def __init__(self, func):super(Foo, self).__init__()self._func = funcdef __call__(self):print('class decorator')self._func()@Foo def bar():print('bar')bar()運(yùn)行結(jié)果如下:
class decorator bar?
?
內(nèi)置裝飾器
Python 中內(nèi)置的裝飾器有三個(gè):?staticmethod、classmethod?和?property
?
staticmethod?是類靜態(tài)方法,其跟成員方法的區(qū)別是沒有?self?指針,并且可以在類不進(jìn)行實(shí)例化的情況下調(diào)用,
下面是一個(gè)實(shí)例,對(duì)比靜態(tài)方法 和 成員方法:
class Foo(object):def __init__(self):super(Foo, self).__init__()self.msg = 'hello word'@staticmethoddef static_method(msg):print(msg)def member_method(self, msg=None):msg = msg if msg else self.msgprint(msg)foo = Foo() foo.member_method('some msg') foo.static_method('some msg') Foo.static_method('some msg')?
classmethod?與成員方法的區(qū)別在于所接收的第一個(gè)參數(shù)不是?self?類實(shí)例的指針,而是當(dāng)前類的具體類型,
下面是一個(gè)實(shí)例:
class Foo(object):@classmethoddef class_method(cls):print(repr(cls))def member_method(self):print(repr(self))foo = Foo() foo.class_method() foo.member_method()運(yùn)行結(jié)果如下:
<class '__main__.Foo'> <__main__.Foo object at 0x000002895A412508>?
property?是屬性的意思,即可以通過通過類實(shí)例直接訪問的信息,下面是具體的例子:
class Foo(object):def __init__(self, var):super(Foo, self).__init__()self._var = var@propertydef var(self):return self._var@var.setterdef var(self, var):self._var = varfoo = Foo('var 1') print(foo.var) foo.var = 'var 2' print(foo.var)注意:?如果將上面的?@var.setter?裝飾器所裝飾的成員函數(shù)去掉,則?Foo.var?屬性為只讀屬性,使用?foo.var = 'var 2'?進(jìn)行賦值時(shí)會(huì)拋出異常,其運(yùn)行結(jié)果如下:
var 1 var 2?
?
調(diào)用順序
裝飾器的調(diào)用順序與使用?@?語法糖聲明的順序相反,如下所示:
def decorator_a(func):print("decorator_a")return funcdef decorator_b(func):print("decorator_b")return func@decorator_a @decorator_b def foo():print("foo")foo()其等價(jià)于:
def decorator_a(func):print("decorator_a")return funcdef decorator_b(func):print("decorator_b")return funcdef foo():print("foo")foo = decorator_a(decorator_b(foo)) foo()通過等價(jià)的調(diào)用形式我們可以看到,按照 python 的函數(shù)求值序列,decorator_b(fun)?會(huì)首先被求值,然后將其結(jié)果作為輸入,傳遞給?decorator_a,因此其調(diào)用順序與聲明順序相反。
其運(yùn)行結(jié)果如下所示:
decorator_b decorator_a foo?
?
調(diào)用時(shí)機(jī)
裝飾器很好用,那么它什么時(shí)候被調(diào)用?性能開銷怎么樣?會(huì)不會(huì)有副作用?接下來我們就以幾個(gè)實(shí)例來驗(yàn)證我們的猜想。
首先我們驗(yàn)證一下裝飾器的性能開銷,代碼如下所示:
def decorator_a(func):print("decorator_a")print('func id: ' + str(id(func)))return funcdef decorator_b(func):print("decorator_b")print('func id: ' + str(id(func)))return funcprint('Begin declare foo with decorators')@decorator_a @decorator_b def foo():print("foo")print('End declare foo with decorators')print('First call foo') foo()print('Second call foo') foo()print('Function infos') print('decorator_a id: ' + str(id(decorator_a))) print('decorator_b id: ' + str(id(decorator_b))) print('foo id : ' + str(id(foo)))運(yùn)行結(jié)果如下:
Begin declare foo with decorators decorator_b func id: 1474741780056 decorator_a func id: 1474741780056 End declare foo with decorators First call foo foo Second call foo foo Function infos decorator_a id: 1474740396104 decorator_b id: 1474740398552 foo id : 1474741780056?
在運(yùn)行結(jié)果中:
Begin declare foo with decorators decorator_b func id: 1474741780056 decorator_a func id: 1474741780056 End declare foo with decorators證實(shí)了裝飾器的調(diào)用時(shí)機(jī)為: 被裝飾對(duì)象定義時(shí)
?
而運(yùn)行結(jié)果中的:
First call foo foo Second call foo foo證實(shí)了在相同?.py?文件中,裝飾器對(duì)所裝飾的函數(shù)只進(jìn)行一次裝飾,不會(huì)每次調(diào)用相應(yīng)函數(shù)時(shí)都重新裝飾,這個(gè)很容易理解,因?yàn)槠浔举|(zhì)等價(jià)于下面的函數(shù)簽名重新綁定:
foo = decorator_a(decorator_b(foo))?
對(duì)于跨模塊的調(diào)用,我們編寫如下結(jié)構(gòu)的測(cè)試代碼:
.?? ├──?common?? │???├──?decorator.py?? │???├──?__init__.py?? │???├──?mod_a?? │???│???├──?fun_a.py?? │???│???└──?__init__.py?? │???└──?mod_b?? │???????├──?fun_b.py?? │???????└──?__init__.py?? └──?test.py??上述所有模塊中的?__init__.py?文件均為:?# -*- coding: utf-8 -*-
common/mod_a/fun_a.py
# -*- coding: utf-8 -*- # common/mod_a/fun_a.py from common.decorator import foodef fun_a():print('in common.mod_a.fun_a.fun_a call foo') foo()common/mod_b/fun_b.py??
# -*- coding: utf-8 -*- # common/mod_b/fun_b.py from common.decorator import foodef fun_b():print('in common.mod_b.fun_b.fun_b call foo') foo()common/decorator.py??
# -*- coding: utf-8 -*- # common/decorator.pydef decorator_a(func):print('init decorator_a')return func@decorator_a def foo():print('function foo')test.py
# -*- coding: utf-8 -*- # test.pyfrom common.mod_a.fun_a import fun_a from common.mod_b.fun_b import fun_bfun_a() fun_b()上述代碼通過創(chuàng)建?common.mod_a?和?common.mod_b?兩個(gè)子模塊,并調(diào)用?common.decorator?中的?foo?函數(shù),來測(cè)試跨模塊時(shí)裝飾器的工作情況,運(yùn)行?test.py?的結(jié)果如下所示:
init decorator_a in common.mod_a.fun_a.fun_a call foo function foo in common.mod_b.fun_b.fun_b call foo function foo經(jīng)過上面的驗(yàn)證,可以看出,對(duì)于跨模塊的調(diào)用,裝飾器也只會(huì)初始化一次,不過這要?dú)w功于?*.pyc,這與本文主題無關(guān),故不詳述。
?
?
裝飾器副作用
關(guān)于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時(shí)候是我們?cè)O(shè)計(jì)上的問題,
下面給出一個(gè)初學(xué)裝飾器時(shí)大家都會(huì)遇到的一個(gè)問題 —— 丟失函數(shù)元信息:
def decorator_a(func):def inner(*args, **kwargs):res = func(*args, **kwargs)return resreturn inner@decorator_a def foo():"""foo doc:return:"""return 'foo result'print('foo.__module__: ' + str(foo.__module__)) print('foo.__name__: ' + str(foo.__name__)) print('foo.__doc__: ' + str(foo.__doc__)) print(foo())運(yùn)行結(jié)果:
foo.__module__: __main__ foo.__name__: inner foo.__doc__: None foo result可以看到,在使用 decorator_a 對(duì) foo 函數(shù)進(jìn)行裝飾后,foo 的元信息會(huì)丟失,解決方案參見:?functools.wraps
?
?
多個(gè)裝飾器運(yùn)行期行為
前面已經(jīng)講解過裝飾器的調(diào)用順序和調(diào)用時(shí)機(jī),但是被多個(gè)裝飾器裝飾的函數(shù),其運(yùn)行期行為還是有一些細(xì)節(jié)需要說明的,而且很可能其行為會(huì)讓你感到驚訝,下面時(shí)一個(gè)實(shí)例:
def tracer(msg):print("[TRACE] %s" % msg)def logger(func):tracer("logger")def inner(username, password):tracer("inner")print("call %s" % func.__name__)return func(username, password)return innerdef login_debug_helper(show_debug_info=False):tracer("login_debug_helper")def proxy_fun(func):tracer("proxy_fun")def delegate_fun(username, password):tracer("delegate_fun")if show_debug_info:print(f"username:{username}\npassword:{password}")return func(username, password)return delegate_funreturn proxy_funprint('Declaring login_a')@logger @login_debug_helper(show_debug_info=True) def login_a(username, password):tracer("login_a")print("do some login authentication")return Trueprint('Call login_a') login_a("mdl", "pwd")大家先來看一下運(yùn)行結(jié)果,看看是不是跟自己想象中的一致:
Declaring login_a [TRACE] login_debug_helper [TRACE] proxy_fun [TRACE] logger Call login_a [TRACE] inner call delegate_fun [TRACE] delegate_fun username:mdl password:pwd [TRACE] login_a do some login authentication首先,裝飾器初始化時(shí)的調(diào)用順序與我們前面講解的一致,如下:
Declaring login_a [TRACE] login_debug_helper [TRACE] proxy_fun [TRACE] logger然而,接下來,來自?logger?裝飾器中的?inner?函數(shù)首先被執(zhí)行,然后才是login_debug_helper?返回的?proxy_fun?中的?delegate_fun?函數(shù)。各位讀者發(fā)現(xiàn)了嗎,運(yùn)行期執(zhí)行login_a?函數(shù)的時(shí)候,裝飾器中返回的函數(shù)的執(zhí)行順序是相反的,難道是我們前面講解的例子有錯(cuò)誤嗎?其實(shí),如果大家的認(rèn)為運(yùn)行期調(diào)用順序應(yīng)該與裝飾器初始化階段的順序一致的話,那說明大家沒有看透這段代碼的調(diào)用流程,下面我來為大家分析一下。
def login_debug_helper(show_debug_info=False):tracer("login_debug_helper")def proxy_fun(func):tracer("proxy_fun")def delegate_fun(username, password):tracer("delegate_fun")if show_debug_info:print(f"username:{username}\npassword:{password}")return func(username, password)return delegate_funreturn proxy_fun當(dāng)裝飾器?login_debug_helper?被調(diào)用時(shí),其等價(jià)于:
gin_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')對(duì)于只有?login_debug_helper?的情況,現(xiàn)在就應(yīng)該是執(zhí)行完?login_a?輸出結(jié)果的時(shí)刻了,但是如果現(xiàn)在在加上?logger?裝飾器的話,那么這個(gè)?login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')?就被延遲執(zhí)行,而將?login_debug_helper(show_debug_info=True)(login_a)?作為參數(shù)傳遞給?logger,我們令?login_tmp = login_debug_helper(show_debug_info=True)(login_a),則調(diào)用過程等價(jià)于:
login_tmp = login_debug_helper(show_debug_info=True)(login_a) login_a = logger(login_tmp) login_a('mdl', 'pwd')相信大家看過上面的等價(jià)變換后,已經(jīng)明白問題出在哪里了,如果你還沒有明白,我強(qiáng)烈建議你把這個(gè)例子自己敲一遍,并嘗試用自己的方式進(jìn)行化簡(jiǎn),逐步得出結(jié)論。
?
一些實(shí)例參考
本文主要講解原理性的東西,具體的實(shí)例可以參考下面的鏈接:
Python裝飾器實(shí)例:調(diào)用參數(shù)合法性驗(yàn)證
Python裝飾器與面向切面編程
Python裝飾器小結(jié)
Python tips: 超時(shí)裝飾器, @timeout decorator
python中判斷一個(gè)運(yùn)行時(shí)間過長的函數(shù)
python利用裝飾器和threading實(shí)現(xiàn)異步調(diào)用
python輸出指定函數(shù)運(yùn)行時(shí)間的裝飾器
python通過裝飾器和線程限制函數(shù)的執(zhí)行時(shí)間
python裝飾器的一個(gè)妙用
通過 Python 裝飾器實(shí)現(xiàn)DRY(不重復(fù)代碼)原則
參考資料
Understanding Python Decorators in 12 Easy Steps
Decorators and Functional Python
Python Wiki: PythonDecorators
Meta-matters: Using decorators for better Python programming
Python裝飾器入門(譯)
Python裝飾器與面向切面編程
Python 的閉包和裝飾器
Python裝飾器學(xué)習(xí)(九步入門)
python 裝飾器和 functools 模塊
?
?
?
理解 Python 中的裝飾器
?
From:https://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html
?
文章先由 stackoverflow上面的一個(gè)問題引起吧,如果使用如下的代碼:
@makebold @makeitalic def say():return "Hello"打印出如下的輸出:
<b><i>Hello<i></b>你會(huì)怎么做?最后給出的答案是:
def make_bold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef make_italic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@make_bold @make_italic def hello():return "hello world"print(hello()) # 結(jié)果: <b><i>hello world</i></b>現(xiàn)在我們來看看如何從一些最基礎(chǔ)的方式來理解Python的裝飾器。英文討論參考Here。
裝飾器是一個(gè)很著名的設(shè)計(jì)模式,經(jīng)常被用于有切面需求的場(chǎng)景,較為經(jīng)典的有插入日志、性能測(cè)試、事務(wù)處理等。裝飾器是解決這類問題的絕佳設(shè)計(jì),有了裝飾器,我們就可以抽離出大量函數(shù)中與函數(shù)功能本身無關(guān)的雷同代碼并繼續(xù)重用。概括的講,裝飾器的作用就是為已經(jīng)存在的對(duì)象添加額外的功能。
?
1.1. 需求是怎么來的 ?
裝飾器的定義很是抽象,我們來看一個(gè)小例子。
def foo():print('in foo()')foo()這是一個(gè)很無聊的函數(shù)沒錯(cuò)。但是突然有一個(gè)更無聊的人,我們稱呼他為B君,說我想看看執(zhí)行這個(gè)函數(shù)用了多長時(shí)間,好吧,那么我們可以這樣做:
import timedef foo():start = time.clock()print('in foo()') end = time.clock()print('used:', end - start) foo()很好,功能看起來無懈可擊??墒堑疤鄣腂君此刻突然不想看這個(gè)函數(shù)了,他對(duì)另一個(gè)叫foo2的函數(shù)產(chǎn)生了更濃厚的興趣。
怎么辦呢?如果把以上新增加的代碼復(fù)制到foo2里,這就犯了大忌了~復(fù)制什么的難道不是最討厭了么!而且,如果B君繼續(xù)看了其他的函數(shù)呢?
?
1.2. 以不變應(yīng)萬變,是變也
還記得嗎,函數(shù)在Python中是一等公民,那么我們可以考慮重新定義一個(gè)函數(shù)timeit,將foo的引用傳遞給他,然后在timeit中調(diào)用foo并進(jìn)行計(jì)時(shí),這樣,我們就達(dá)到了不改動(dòng)foo定義的目的,而且,不論B君看了多少個(gè)函數(shù),我們都不用去修改函數(shù)定義了!
import timedef foo():print('in foo()') def timeit(func):start = time.clock()func()end = time.clock()print('used:', end - start) timeit(foo)看起來邏輯上并沒有問題,一切都很美好并且運(yùn)作正常!……等等,我們似乎修改了調(diào)用部分的代碼。原本我們是這樣調(diào)用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調(diào)用了,你就不得不去修改這N處的代碼?;蛘吒鼧O端的,考慮其中某處調(diào)用的代碼無法修改這個(gè)情況,比如:這個(gè)函數(shù)是你交給別人使用的。
?
1.3. 最大限度地少改動(dòng)!
既然如此,我們就來想想辦法不修改調(diào)用的代碼;如果不修改調(diào)用代碼,也就意味著調(diào)用foo()需要產(chǎn)生調(diào)用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個(gè)參數(shù)……想辦法把參數(shù)統(tǒng)一吧!如果timeit(foo)不是直接產(chǎn)生調(diào)用效果,而是返回一個(gè)與foo參數(shù)列表一致的函數(shù)的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調(diào)用foo()的代碼完全不用修改!
# -*- coding: UTF-8 -*- import timedef 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.perf_counter()func()end = time.perf_counter()print('used:', end - start)# 將包裝后的函數(shù)返回return wrapperfoo = timeit(foo) foo()這樣,一個(gè)簡(jiǎn)易的計(jì)時(shí)器就做好了!我們只需要在定義foo以后調(diào)用foo之前,加上foo = timeit(foo),就可以達(dá)到計(jì)時(shí)的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個(gè)例子中,函數(shù)進(jìn)入和退出時(shí)需要計(jì)時(shí),這被稱為一個(gè)橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統(tǒng)編程習(xí)慣的從上往下執(zhí)行方式相比較而言,像是在函數(shù)執(zhí)行的流程中橫向地插入了一段邏輯。在特定的業(yè)務(wù)領(lǐng)域里,能減少大量重復(fù)代碼。面向切面編程還有相當(dāng)多的術(shù)語,這里就不多做介紹,感興趣的話可以去找找相關(guān)的資料。
這個(gè)例子僅用于演示,并沒有考慮foo帶有參數(shù)和有返回值的情況,完善它的重任就交給你了 :)
上面這段代碼看起來似乎已經(jīng)不能再精簡(jiǎn)了,Python于是提供了一個(gè)語法糖來降低字符輸入量。
重點(diǎn)關(guān)注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價(jià)
千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個(gè)額外的好處:這樣看上去更有裝飾器的感覺。
-------------------
?
Python中函數(shù)也是被視為對(duì)象(Python 中一切 皆 對(duì)象)
要理解python的裝飾器,我們首先必須明白在Python中函數(shù)也是被視為對(duì)象。這一點(diǎn)很重要。先看一個(gè)例子:
def shout(word="yes"):return word.capitalize() + " !"print(shout()) # 輸出 : 'Yes !'# 作為一個(gè)對(duì)象,你可以把函數(shù)賦給任何其他對(duì)象變量 scream = shout# 注意我們沒有使用圓括號(hào),因?yàn)槲覀儾皇窃谡{(diào)用函數(shù) # 我們把函數(shù)shout賦給scream,也就是說你可以通過scream調(diào)用shout print(scream()) # 輸出 : 'Yes !'# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數(shù) del shout try:print(shout()) except BaseException as e:print(e)# 輸出 : "name 'shout' is not defined"print(scream()) # 輸出 : 'Yes !'我們暫且把這個(gè)話題放旁邊,我們先看看 python 另外一個(gè)很有意思的屬性:可以在函數(shù)中定義函數(shù):
def talk():# 你可以在talk中定義另外一個(gè)函數(shù)def whisper(word="yes"):return word.lower() + "..."# ... 并且立馬使用它print(whisper())# 你每次調(diào)用'talk',定義在talk里面的whisper同樣也會(huì)被調(diào)用 talk() # 輸出 : # yes...# 但是"whisper" 不會(huì)單獨(dú)存在: try:print(whisper()) except BaseException as e:print(e)# 輸出 : "name 'whisper' is not defined"*?
函數(shù)引用
從以上兩個(gè)例子我們可以得出,函數(shù)既然作為一個(gè)對(duì)象,因此:
- 1. 其可以被賦給其他變量
- 2. 其可以被定義在另外一個(gè)函數(shù)內(nèi)
這也就是說,函數(shù)可以返回一個(gè)函數(shù),看下面的例子:
def get_talk(type="shout"):# 我們定義另外一個(gè)函數(shù)def shout(word="yes"):return word.capitalize() + " !"def whisper(word="yes"):return word.lower() + "..."# 然后我們返回其中一個(gè)if type == "shout":# 我們沒有使用(),因?yàn)槲覀儾皇窃谡{(diào)用該函數(shù)# 我們是在返回該函數(shù)return shoutelse:return whisper# 然后怎么使用呢 ? # 把該函數(shù)賦予某個(gè)變量 talk = get_talk()# 這里你可以看到talk其實(shí)是一個(gè)函數(shù)對(duì)象: print(talk) # 輸出 : <function shout at 0xb7ea817c># 該對(duì)象由函數(shù)返回的其中一個(gè)對(duì)象: print(talk())# 或者你可以直接如下調(diào)用 : print(get_talk("whisper")()) # 輸出 : yes...還有,既然可以返回一個(gè)函數(shù),我們可以把它作為參數(shù)傳遞給函數(shù):
def do_something_before(func):print("I do something before then I call the function you gave me")print(func())do_something_before(scream) # 輸出 : # I do something before then I call the function you gave me # Yes !這里你已經(jīng)足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執(zhí)行代碼而無須改變函數(shù)本身內(nèi)容。
?
?
手工裝飾
那么如何進(jìn)行手動(dòng)裝飾呢?
# 裝飾器是一個(gè)函數(shù),而其參數(shù)為另外一個(gè)函數(shù) def my_shiny_new_decorator(a_function_to_decorate):# 在內(nèi)部定義了另外一個(gè)函數(shù):一個(gè)封裝器。# 這個(gè)函數(shù)將原始函數(shù)進(jìn)行封裝,所以你可以在它之前或者之后執(zhí)行一些代碼def the_wrapper_around_the_original_function():# 放一些你希望在真正函數(shù)執(zhí)行前的一些代碼print("Before the function runs")# 執(zhí)行原始函數(shù)a_function_to_decorate()# 放一些你希望在原始函數(shù)執(zhí)行后的一些代碼print("After the function runs")# 在此刻,"a_function_to_decrorate"還沒有被執(zhí)行,我們返回了創(chuàng)建的封裝函數(shù)# 封裝器包含了函數(shù)以及其前后執(zhí)行的代碼,其已經(jīng)準(zhǔn)備完畢return the_wrapper_around_the_original_function# 現(xiàn)在想象下,你創(chuàng)建了一個(gè)你永遠(yuǎn)也不遠(yuǎn)再次接觸的函數(shù) def a_stand_alone_function():print("I am a stand alone function, don't you dare modify me")a_stand_alone_function() # 輸出: I am a stand alone function, don't you dare modify me# 好了,你可以封裝它實(shí)現(xiàn)行為的擴(kuò)展??梢院?jiǎn)單的把它丟給裝飾器 # 裝飾器將動(dòng)態(tài)地把它和你要的代碼封裝起來,并且返回一個(gè)新的可用的函數(shù)。 a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function_decorated() # 輸出 : # Before the function runs # I am a stand alone function, don't you dare modify me # After the function runs# 現(xiàn)在你也許要求當(dāng)每次調(diào)用a_stand_alone_function時(shí), # 實(shí)際調(diào)用卻是a_stand_alone_function_decorated。 # 實(shí)現(xiàn)也很簡(jiǎn)單,可以用my_shiny_new_decorator來給a_stand_alone_function重新賦值。 a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function) a_stand_alone_function() # 輸出 : # Before the function runs # I am a stand alone function, don't you dare modify me # After the function runs # And guess what, that's EXACTLY what decorators do !?
?
裝飾器揭秘
前面的例子,我們可以使用裝飾器的語法:
@my_shiny_new_decorator def another_stand_alone_function():print("Leave me alone")another_stand_alone_function() # 輸出 : # Before the function runs # Leave me alone # After the function runs當(dāng)然你也可以累積裝飾:
def bread(func):def wrapper():print(r"</''''''\>")func()print(r"<\______/>")return wrapperdef ingredients(func):def wrapper():print("#tomatoes#")func()print("~salad~")return wrapperdef sandwich(food="--ham--"):print(food) sandwich() # 輸出 : --ham-- sandwich = bread(ingredients(sandwich)) sandwich() # outputs : # </''''''\> # #tomatoes# # --ham-- # ~salad~ # <\______/>?
?
使用 python 裝飾器語法:
@bread @ingredients def sandwich(food="--ham--"):print(food)sandwich() # 輸出 : # </''''''\> # #tomatoes# # --ham-- # ~salad~ # <\______/>?
裝飾器的順序很重要,需要注意:
@ingredients @bread def strange_sandwich(food="--ham--"):print(food) strange_sandwich() # 輸出 : # tomatoes# # </''''''\> # --ham-- # <\______/> # ~salad~?
最后回答前面提到的問題:
# 裝飾器 make_bold 用于轉(zhuǎn)換為粗體 def make_bold(fn):# 結(jié)果返回該函數(shù)def wrapper():# 插入一些執(zhí)行前后的代碼return "<b>" + fn() + "</b>"return wrapper# 裝飾器 make_italic 用于轉(zhuǎn)換為斜體 def make_italic(fn):# 結(jié)果返回該函數(shù)def wrapper():# 插入一些執(zhí)行前后的代碼return "<i>" + fn() + "</i>"return wrapper@make_bold @make_italic def say():return "hello"print(say()) # 輸出: <b><i>hello</i></b># 等同于def say():return "hello"say = make_bold(make_italic(say)) print(say()) # 輸出: <b><i>hello</i></b>?
?
內(nèi)置的裝飾器
內(nèi)置的裝飾器有三個(gè),分別是staticmethod、classmethod和property,作用分別是把類中定義的實(shí)例方法變成靜態(tài)方法、類方法和類屬性。由于模塊里可以定義函數(shù),所以靜態(tài)方法和類方法的用處并不是太多,除非你想要完全的面向?qū)ο缶幊獭6鴮傩砸膊皇遣豢苫蛉钡?#xff0c;Java沒有屬性也一樣活得很滋潤。從我個(gè)人的Python經(jīng)驗(yàn)來看,我沒有使用過property,使用staticmethod和classmethod的頻率也非常低。
class Rabbit(object):def __init__(self, name):self._name = name@staticmethoddef newRabbit(name):return Rabbit(name)@classmethoddef newRabbit2(cls):return Rabbit('')@propertydef name(self):return self._name這里定義的屬性是一個(gè)只讀屬性,如果需要可寫,則需要再定義一個(gè)setter:
@name.setter
def name(self, name):
? ? self._name = name
functools模塊
functools模塊提供了兩個(gè)裝飾器。這個(gè)模塊是Python 2.5后新增的,一般來說大家用的應(yīng)該都高于這個(gè)版本。但我平時(shí)的工作環(huán)境是2.4 T-T
2.3.1. wraps(wrapped[, assigned][, updated]):?
這是一個(gè)很有用的裝飾器??催^前一篇反射的朋友應(yīng)該知道,函數(shù)是有幾個(gè)特殊屬性比如函數(shù)名,在被裝飾后,上例中的函數(shù)名foo會(huì)變成包裝函數(shù)的名字wrapper,如果你希望使用反射,可能會(huì)導(dǎo)致意外的結(jié)果。這個(gè)裝飾器可以解決這個(gè)問題,它能將裝飾過的函數(shù)的特殊屬性保留。
首先注意第5行,如果注釋這一行,foo.__name__將是'wrapper'。另外相信你也注意到了,這個(gè)裝飾器竟然帶有一個(gè)參數(shù)。實(shí)際上,他還有另外兩個(gè)可選的參數(shù),assigned中的屬性名將使用賦值的方式替換,而updated中的屬性名將使用update的方式合并,你可以通過查看functools的源代碼獲得它們的默認(rèn)值。對(duì)于這個(gè)裝飾器,相當(dāng)于wrapper = functools.wraps(func)(wrapper)。
?
?
?
深入淺出 Python 裝飾器:16 步輕松搞定 Python 裝飾器
?
而Python使用了一種相對(duì)于Decorator Pattern和Annotation來說非常優(yōu)雅的方法,這種方法不需要你去掌握什么復(fù)雜的OO模型或是Annotation的各種類庫規(guī)定,完全就是語言層面的玩法:一種函數(shù)式編程的技巧。如果你看過本站的《函數(shù)式編程》,你一定會(huì)為函數(shù)式編程的那種“描述你想干什么,而不是描述你要怎么去實(shí)現(xiàn)”的編程方式感到暢快。(如果你不了解函數(shù)式編程,那在讀本文之前,還請(qǐng)你移步去看看《函數(shù)式編程》) 好了。
?
1. 函數(shù)
在python中,函數(shù)通過def關(guān)鍵字、函數(shù)名和可選的參數(shù)列表定義。通過return關(guān)鍵字返回值。我們舉例來說明如何定義和調(diào)用一個(gè)簡(jiǎn)單的函數(shù):
def foo():return 1 foo() 1方法體(當(dāng)然多行也是一樣的)是必須的,通過縮進(jìn)來表示,在方法名的后面加上雙括號(hào)()就能夠調(diào)用函數(shù)
?
2. 作用域
python中,函數(shù)會(huì)創(chuàng)建一個(gè)新的作用域。python開發(fā)者可能會(huì)說函數(shù)有自己的命名空間,差不多一個(gè)意思。這意味著在函數(shù)內(nèi)部碰到一個(gè)變量的時(shí)候函數(shù)會(huì)優(yōu)先在自己的命名空間里面去尋找。讓我們寫一個(gè)簡(jiǎn)單的函數(shù)看一下 本地作用域 和 全局作用域有什么不同:
a_string = "This is a global variable" def foo():print locals() print globals() {..., 'a_string': 'This is a global variable'} foo() # 2 {}內(nèi)置的函數(shù)globals返回一個(gè)包含所有python解釋器知道的變量名稱的字典(為了干凈和洗的白白的,我省略了python自行創(chuàng)建的一些變量)。在#2我調(diào)用了函數(shù) foo 把函數(shù)內(nèi)部本地作用域里面的內(nèi)容打印出來。我們能夠看到,函數(shù)foo有自己獨(dú)立的命名空間,雖然暫時(shí)命名空間里面什么都還沒有。
?
3. 變量解析規(guī)則
當(dāng)然這并不是說我們?cè)诤瘮?shù)里面就不能訪問外面的全局變量。在python的作用域規(guī)則里面,創(chuàng)建變量一定會(huì)在當(dāng)前作用域里創(chuàng)建一個(gè)變量,但是訪問或者修改變量時(shí)會(huì)先在當(dāng)前作用域查找變量,沒有找到匹配變量的話會(huì)依次向上在閉合的作用域里面進(jìn)行查看找。所以如果我們修改函數(shù)foo的實(shí)現(xiàn)讓它打印全局的作用域里的變量也是可以的:
a_string = "This is a global variable" def foo():print a_string # 1 foo() This is a global variable在#1處,python解釋器會(huì)嘗試查找變量a_string,當(dāng)然在函數(shù)的本地作用域里面找不到,所以接著會(huì)去上層的作用域里面去查找。
但是另一方面,假如我們?cè)诤瘮?shù)內(nèi)部給全局變量賦值,結(jié)果卻和我們想的不一樣:
a_string = "This is a global variable" def foo():a_string = "test" # 1print locals() foo() {'a_string': 'test'} a_string # 2 'This is a global variable'我們能夠看到,全局變量能夠被訪問到(如果是可變數(shù)據(jù)類型(像list,dict這些)甚至能夠被更改)但是賦值不行。在函數(shù)內(nèi)部的#1處,我們實(shí)際上新創(chuàng)建了一個(gè)局部變量,隱藏全局作用域中的同名變量。我們可以通過打印出局部命名空間中的內(nèi)容得出這個(gè)結(jié)論。我們也能看到在#2處打印出來的變量a_string的值并沒有改變。
?
4. 變量生存周期
值得注意的一個(gè)點(diǎn)是,變量不僅是生存在一個(gè)個(gè)的命名空間內(nèi),他們都有自己的生存周期,請(qǐng)看下面這個(gè)例子:
def foo():x = 1 foo() print x # 1 #Traceback (most recent call last): #NameError: name 'x' is not defined#1處發(fā)生的錯(cuò)誤不僅僅是因?yàn)樽饔糜蛞?guī)則導(dǎo)致的(盡管這是拋出了NameError的錯(cuò)誤的原因)它還和python以及其它很多編程語言中函數(shù)調(diào)用實(shí)現(xiàn)的機(jī)制有關(guān)。在這個(gè)地方這個(gè)執(zhí)行時(shí)間點(diǎn)并沒有什么有效的語法讓我們能夠獲取變量x的值,因?yàn)樗@個(gè)時(shí)候壓根不存在!函數(shù)foo的命名空間隨著函數(shù)調(diào)用開始而開始,結(jié)束而銷毀。
?
5. 函數(shù)參數(shù)
python允許我們向函數(shù)傳遞參數(shù),參數(shù)會(huì)變成本地變量存在于函數(shù)內(nèi)部。
def foo(x):print locals() foo(1) {'x': 1}在Python里有很多的方式來定義和傳遞參數(shù),完整版可以查看 python官方文檔。我們這里簡(jiǎn)略的說明一下:函數(shù)的參數(shù)可以是必須的位置參數(shù)或者是可選的命名,默認(rèn)參數(shù)。
def foo(x, y=0): # 1return x - y foo(3, 1) # 2 2 foo(3) # 3 3 foo() # 4 #Traceback (most recent call last): #TypeError: foo() takes at least 1 argument (0 given) foo(y=1, x=3) # 5 2在#1處我們定義了函數(shù)foo,它有一個(gè)位置參數(shù)x和一個(gè)命名參數(shù)y。在#2處我們能夠通過常規(guī)的方式來調(diào)用函數(shù),盡管有一個(gè)命名參數(shù),但參數(shù)依然可以通過位置傳遞給函數(shù)。在調(diào)用函數(shù)的時(shí)候,對(duì)于命名參數(shù)y我們也可以完全不管就像#3處所示的一樣。如果命名參數(shù)沒有接收到任何值的話,python會(huì)自動(dòng)使用聲明的默認(rèn)值也就是0。需要注意的是我們不能省略第一個(gè)位置參數(shù)x, 否則的話就會(huì)像#4處所示發(fā)生錯(cuò)誤。
目前還算簡(jiǎn)潔清晰吧, 但是接下來可能會(huì)有點(diǎn)令人困惑。python支持函數(shù)調(diào)用時(shí)的命名參數(shù)(個(gè)人覺得應(yīng)該是命名實(shí)參)??纯?5處的函數(shù)調(diào)用,我們傳遞的是兩個(gè)命名實(shí)參,這個(gè)時(shí)候因?yàn)橛忻Q標(biāo)識(shí),參數(shù)傳遞的順序也就不用在意了。
當(dāng)然相反的情況也是正確的:函數(shù)的第二個(gè)形參是y,但是我們通過位置的方式傳遞值給它。在#2處的函數(shù)調(diào)用foo(3,1),我們把3傳遞給了第一個(gè)參數(shù),把1傳遞給了第二個(gè)參數(shù),盡管第二個(gè)參數(shù)是一個(gè)命名參數(shù)。
一個(gè)簡(jiǎn)單的概念:函數(shù)的參數(shù)可以有名稱和位置。這意味著在函數(shù)的定義和調(diào)用的時(shí)候會(huì)稍稍在理解上有點(diǎn)兒不同。我們可以給只定義了位置參數(shù)的函數(shù)傳遞命名參數(shù)(實(shí)參),反之亦然!如果覺得不夠可以查看官方文檔
?
6. 嵌套函數(shù)
Python允許創(chuàng)建嵌套函數(shù)。這意味著我們可以在函數(shù)里面定義函數(shù)而且現(xiàn)有的作用域和變量生存周期依舊適用。
def outer():x = 1def inner():print x # 1return inner() # 2 outer() 1這個(gè)例子有一點(diǎn)兒復(fù)雜,但是看起來也還行。想一想在#1發(fā)生了什么:python解釋器需找一個(gè)叫x的本地變量,查找失敗之后會(huì)繼續(xù)在上層的作用域里面尋找,這個(gè)上層的作用域定義在另外一個(gè)函數(shù)里面。對(duì)函數(shù)outer來說,變量x是一個(gè)本地變量,但是如先前提到的一樣,函數(shù)inner可以訪問封閉的作用域(至少可以讀和修改)。在#2處,我們調(diào)用函數(shù)inner,非常重要的一點(diǎn)是,inner也僅僅是一個(gè)遵循python變量解析規(guī)則的變量名,python解釋器會(huì)優(yōu)先在outer的作用域里面對(duì)變量名inner查找匹配的變量.
?
7. 函數(shù)是 python 世界里的一級(jí)類對(duì)象
顯而易見,在python里函數(shù)和其他東西一樣都是對(duì)象。(此處應(yīng)該大聲歌唱)啊!包含變量的函數(shù),你也并不是那么特殊!
issubclass(int, object) # all objects in Python inherit from a common baseclass #True def foo():pass foo.__class__ # 1 #<type 'function'> issubclass(foo.__class__, object) #True你也許從沒有想過,你定義的函數(shù)居然會(huì)有屬性。沒辦法,函數(shù)在python里面就是對(duì)象(Python一切皆對(duì)象),和其他的東西一樣,也許這樣描述會(huì)太學(xué)院派太官方了點(diǎn):在python里,函數(shù)只是一些普通的值而已和其他的值一毛一樣。這就是說你可以把函數(shù)像參數(shù)一樣傳遞給其他的函數(shù)或者說從函數(shù)里面返回函數(shù)!如果你從來沒有這么想過,那看看下面這個(gè)例子:
def add(x, y):return x + y def sub(x, y):return x - y def apply(func, x, y): # 1return func(x, y) # 2 apply(add, 2, 1) # 3 3 apply(sub, 2, 1) 1這個(gè)例子對(duì)你來說應(yīng)該不會(huì)很奇怪。add和sub是非常普通的兩個(gè)python函數(shù),接受兩個(gè)值,返回一個(gè)計(jì)算后的結(jié)果值。在#1處你們能看到準(zhǔn)備接收一個(gè)函數(shù)的變量只是一個(gè)普通的變量而已,和其他變量一樣。在#2處我們調(diào)用傳進(jìn)來的函數(shù):“()代表著調(diào)用的操作并且調(diào)用變量包含的值。在#3處,你們也能看到傳遞函數(shù)并沒有什么特殊的語法。” 函數(shù)的名稱只是很其他變量一樣的表標(biāo)識(shí)符而已。
你們也許看到過這樣的行為:“python把頻繁要用的操作變成函數(shù)作為參數(shù)進(jìn)行使用,像通過傳遞一個(gè)函數(shù)給內(nèi)置排序函數(shù)的key參數(shù)從而來自定義排序規(guī)則。那把函數(shù)當(dāng)做返回值回事這樣的情況呢:
def outer():def inner():print "Inside inner"return inner # 1 foo = outer() #2 foo #<function inner at 0x...> foo() #Inside inner這個(gè)例子看起來也許會(huì)更加的奇怪。在#1處我把恰好是函數(shù)標(biāo)識(shí)符的變量inner作為返回值返回出來。這并沒有什么特殊的語法:”把函數(shù)inner返回出來,否則它根本不可能會(huì)被調(diào)用到?!斑€記得變量的生存周期嗎?每次函數(shù)outer被調(diào)用的時(shí)候,函數(shù)inner都會(huì)被重新定義,如果它不被當(dāng)做變量返回的話,每次執(zhí)行過后它將不復(fù)存在。
在#2處我們捕獲住返回值 – 函數(shù)inner,將它存在一個(gè)新變量foo里。我們能夠看到,當(dāng)對(duì)變量foo進(jìn)行求值,它確實(shí)包含函數(shù)inner,而且我們能夠?qū)λM(jìn)行調(diào)用。初次看起來可能會(huì)覺得有點(diǎn)奇怪,但是理解起來并不困難是吧。堅(jiān)持住,因?yàn)槠婀值霓D(zhuǎn)折馬上就要來了
?
8. 閉包
我們先不急著定義什么是閉包,先來看看一段代碼,僅僅是把上一個(gè)例子簡(jiǎn)單的調(diào)整了一下:
def outer():x = 1def inner():print x # 1return inner foo = outer() foo.func_closure #(<cell at 0x...: int object at 0x...>,)在上一個(gè)例子中我們了解到,inner作為一個(gè)函數(shù)被outer返回,保存在一個(gè)變量foo,并且我們能夠?qū)λM(jìn)行調(diào)用foo()。不過它會(huì)正常的運(yùn)行嗎?我們先來看看作用域規(guī)則。
所有的東西都在python的作用域規(guī)則下進(jìn)行工作:“x是函數(shù)outer里的一個(gè)局部變量。當(dāng)函數(shù)inner在#1處打印x的時(shí)候,python解釋器會(huì)在inner內(nèi)部查找相應(yīng)的變量,當(dāng)然會(huì)找不到,所以接著會(huì)到封閉作用域里面查找,并且會(huì)找到匹配。
但是從變量的生存周期來看,該怎么理解呢?我們的變量x是函數(shù)outer的一個(gè)本地變量,這意味著只有當(dāng)函數(shù)outer正在運(yùn)行的時(shí)候才會(huì)存在。根據(jù)我們已知的python運(yùn)行模式,我們沒法在函數(shù)outer返回之后繼續(xù)調(diào)用函數(shù)inner,在函數(shù)inner被調(diào)用的時(shí)候,變量x早已不復(fù)存在,可能會(huì)發(fā)生一個(gè)運(yùn)行時(shí)錯(cuò)誤。
萬萬沒想到,返回的函數(shù)inner居然能夠正常工作。Python支持一個(gè)叫做函數(shù)閉包的特性,用人話來講就是,嵌套定義在非全局作用域里面的函數(shù)能夠記住它在被定義的時(shí)候它所處的封閉命名空間。這能夠通過查看函數(shù)的func_closure屬性得出結(jié)論,這個(gè)屬性里面包含封閉作用域里面的值(只會(huì)包含被捕捉到的值,比如x,如果在outer里面還定義了其他的值,封閉作用域里面是不會(huì)有的)
記住,每次函數(shù)outer被調(diào)用的時(shí)候,函數(shù)inner都會(huì)被重新定義。現(xiàn)在變量x的值不會(huì)變化,所以每次返回的函數(shù)inner會(huì)是同樣的邏輯,假如我們稍微改動(dòng)一下呢?
def outer(x):def inner():print x # 1return inner print1 = outer(1) print2 = outer(2) print1() 1 print2() 2從這個(gè)例子中你能夠看到閉包 – 被函數(shù)記住的封閉作用域 – 能夠被用來創(chuàng)建自定義的函數(shù),本質(zhì)上來說是一個(gè)硬編碼的參數(shù)。事實(shí)上我們并不是傳遞參數(shù)1或者2給函數(shù)inner,我們實(shí)際上是創(chuàng)建了能夠打印各種數(shù)字的各種自定義版本。
閉包單獨(dú)拿出來就是一個(gè)非常強(qiáng)大的功能, 在某些方面,你也許會(huì)把它當(dāng)做一個(gè)類似于面向?qū)ο蟮募夹g(shù):outer像是給inner服務(wù)的構(gòu)造器,x像一個(gè)私有變量。使用閉包的方式也有很多:你如果熟悉python內(nèi)置排序方法的參數(shù)key,你說不定已經(jīng)寫過一個(gè)lambda方法在排序一個(gè)列表的列表的時(shí)候基于第二個(gè)元素而不是第一個(gè)?,F(xiàn)在你說不定也可以寫一個(gè)itemgetter方法,接收一個(gè)索引值來返回一個(gè)完美的函數(shù),傳遞給排序函數(shù)的參數(shù)key。
不過,我們現(xiàn)在不會(huì)用閉包做這么low的事(⊙o⊙)…!相反,讓我們?cè)偎淮?#xff0c;寫一個(gè)高大上的裝飾器!
?
9. 裝飾器
裝飾器其實(shí)就是一個(gè)閉包,把一個(gè)函數(shù)當(dāng)做參數(shù)然后返回一個(gè)替代版函數(shù)。我們一步步從簡(jiǎn)到繁來瞅瞅:
def outer(some_func):def inner():print "before some_func"ret = some_func() # 1return ret + 1return inner def foo():return 1 decorated = outer(foo) # 2 decorated() #before some_func #2仔細(xì)看看上面這個(gè)裝飾器的例子。們定義了一個(gè)函數(shù)outer,它只有一個(gè)some_func的參數(shù),在他里面我們定義了一個(gè)嵌套的函數(shù)inner。inner會(huì)打印一串字符串,然后調(diào)用some_func,在#1處得到它的返回值。在outer每次調(diào)用的時(shí)候some_func的值可能會(huì)不一樣,但是不管some_func的之如何,我們都會(huì)調(diào)用它。最后,inner返回some_func() + 1的值 – 我們通過調(diào)用在#2處存儲(chǔ)在變量decorated里面的函數(shù)能夠看到被打印出來的字符串以及返回值2,而不是期望中調(diào)用函數(shù)foo得到的返回值1。
我們可以認(rèn)為變量decorated是函數(shù)foo的一個(gè)裝飾版本,一個(gè)加強(qiáng)版本。事實(shí)上如果打算寫一個(gè)有用的裝飾器的話,我們可能會(huì)想愿意用裝飾版本完全取代原先的函數(shù)foo,這樣我們總是會(huì)得到我們的”加強(qiáng)版“foo。想要達(dá)到這個(gè)效果,完全不需要學(xué)習(xí)新的語法,簡(jiǎn)單地賦值給變量foo就行了:
foo = outer(foo) foo # doctest: +ELLIPSIS #<function inner at 0x...>現(xiàn)在,任何怎么調(diào)用都不會(huì)牽扯到原先的函數(shù)foo,都會(huì)得到新的裝飾版本的foo。
假設(shè)有如下函數(shù):
def now():print '2013-12-25' f = now f() #2013-12-25現(xiàn)在假設(shè)我們要增強(qiáng)now()函數(shù)的功能,比如,在函數(shù)調(diào)用前后自動(dòng)打印日志,但又不希望修改now()函數(shù)的定義,這種在代碼運(yùn)行期間動(dòng)態(tài)增加功能的方式,稱之為“裝飾器”(Decorator)。
本質(zhì)上,decorator就是一個(gè)返回函數(shù)的高階函數(shù)。所以,我們要定義一個(gè)能打印日志的decorator,可以定義如下:
def log(func):def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper觀察上面的log,因?yàn)樗且粋€(gè)decorator,所以接受一個(gè)函數(shù)作為參數(shù),并返回一個(gè)函數(shù)。
?
10. 使用 @ 標(biāo)識(shí)符將裝飾器應(yīng)用到函數(shù)
Python2.4支持使用標(biāo)識(shí)符@將裝飾器應(yīng)用在函數(shù)上,只需要在函數(shù)的定義前加上@和裝飾器的名稱。在上一節(jié)的例子里我們是將原本的方法用裝飾后的方法代替:
add = wrapper(add)這種方式能夠在任何時(shí)候?qū)θ我夥椒ㄟM(jìn)行包裝。但是如果我們自定義一個(gè)方法,我們可以使用@進(jìn)行裝飾:
@wrapper def add(a, b):return Coordinate(a.x + b.x, a.y + b.y)需要明白的是,這樣的做法和先前簡(jiǎn)單的用包裝方法替代原有方法是一模一樣的, python只是加了一些語法糖讓裝飾的行為更加的直接明確和優(yōu)雅一點(diǎn)。
多個(gè)decorator
@decorator_one @decorator_two def func():pass相當(dāng)于:
func = decorator_one(decorator_two(func))比如:帶參數(shù)的decorator:
@decorator(arg1, arg2) def func():pass相當(dāng)于:
func = decorator(arg1,arg2)(func)這意味著decorator(arg1, arg2)這個(gè)函數(shù)需要返回一個(gè)“真正的decorator”。
?
11. *args and **kwargs
我們已經(jīng)完成了一個(gè)有用的裝飾器,但是由于硬編碼的原因它只能應(yīng)用在一類具體的方法上,這類方法接收兩個(gè)參數(shù),傳遞給閉包捕獲的函數(shù)。如果我們想實(shí)現(xiàn)一個(gè)能夠應(yīng)用在任何方法上的裝飾器要怎么做呢?再比如,如果我們要實(shí)現(xiàn)一個(gè)能應(yīng)用在任何方法上的類似于計(jì)數(shù)器的裝飾器,不需要改變?cè)蟹椒ǖ娜魏芜壿嫛_@意味著裝飾器能夠接受擁有任何簽名的函數(shù)作為自己的被裝飾方法,同時(shí)能夠用傳遞給它的參數(shù)對(duì)被裝飾的方法進(jìn)行調(diào)用。
非常巧合的是Python正好有支持這個(gè)特性的語法??梢蚤喿x Python Tutorial 獲取更多的細(xì)節(jié)。當(dāng)定義函數(shù)的時(shí)候使用了*,意味著那些通過位置傳遞的參數(shù)將會(huì)被放在帶有*前綴的變量中, 所以:
def one(*args):print args # 1 one() #() one(1, 2, 3) #(1, 2, 3) def two(x, y, *args): # 2print x, y, args two('a', 'b', 'c') #a b ('c',)第一個(gè)函數(shù)one只是簡(jiǎn)單地講任何傳遞過來的位置參數(shù)全部打印出來而已,你們能夠看到,在代碼#1處我們只是引用了函數(shù)內(nèi)的變量args, *args僅僅只是用在函數(shù)定義的時(shí)候用來表示位置參數(shù)應(yīng)該存儲(chǔ)在變量args里面。Python允許我們制定一些參數(shù)并且通過args捕獲其他所有剩余的未被捕捉的位置參數(shù),就像#2處所示的那樣。
*操作符在函數(shù)被調(diào)用的時(shí)候也能使用。意義基本是一樣的。當(dāng)調(diào)用一個(gè)函數(shù)的時(shí)候,一個(gè)用*標(biāo)志的變量意思是變量里面的內(nèi)容需要被提取出來然后當(dāng)做位置參數(shù)被使用。同樣的,來看個(gè)例子:
#1處的代碼和#2處的代碼所做的事情其實(shí)是一樣的,在#2處,python為我們所做的事其實(shí)也可以手動(dòng)完成。這也不是什么壞事,*args要么是表示調(diào)用方法大的時(shí)候額外的參數(shù)可以從一個(gè)可迭代列表中取得,要么就是定義方法的時(shí)候標(biāo)志這個(gè)方法能夠接受任意的位置參數(shù)。
接下來提到的**會(huì)稍多更復(fù)雜一點(diǎn),**代表著鍵值對(duì)的餐宿字典,和*所代表的意義相差無幾,也很簡(jiǎn)單對(duì)不對(duì):
當(dāng)我們定義一個(gè)函數(shù)的時(shí)候,我們能夠用**kwargs來表明,所有未被捕獲的關(guān)鍵字參數(shù)都應(yīng)該存儲(chǔ)在kwargs的字典中。如前所訴,args、kwargs并不是python語法的一部分,但在定義函數(shù)的時(shí)候,使用這樣的變量名算是一個(gè)不成文的約定。和*一樣,我們同樣可以在定義或者調(diào)用函數(shù)的時(shí)候使用**。
dct = {'x': 1, 'y': 2} def bar(x, y):return x + y bar(**dct) #3?
12. 更通用的裝飾器
有了這招新的技能,我們隨隨便便就可以寫一個(gè)能夠記錄下傳遞給函數(shù)參數(shù)的裝飾器了。先來個(gè)簡(jiǎn)單地把日志輸出到界面的例子:
def logger(func):def inner(*args, **kwargs): #1print "Arguments were: %s, %s" % (args, kwargs)return func(*args, **kwargs) #2return inner請(qǐng)注意函數(shù)inner,它能夠接受任意數(shù)量和類型的參數(shù)并把它們傳遞給被包裝的方法,這讓我們能夠用這個(gè)裝飾器來裝飾任何方法。
@logger def foo1(x, y=1):return x * y @logger def foo2():return 2 foo1(5, 4) #Arguments were: (5, 4), {} #20 foo1(1) #Arguments were: (1,), {} #1 foo2() #Arguments were: (), {} #2隨便調(diào)用我們定義的哪個(gè)方法,相應(yīng)的日志也會(huì)打印到輸出窗口,和我們預(yù)期的一樣。
?
13. 帶參數(shù)的裝飾器:
如果decorator本身需要傳入?yún)?shù),那就需要編寫一個(gè)返回decorator的高階函數(shù),寫出來會(huì)更復(fù)雜。比如,要自定義log的文本:
def log(text):def decorator(func):def wrapper(*args, **kw):print '%s %s():' % (text, func.__name__)return func(*args, **kw)return wrapperreturn decorator這個(gè)3層嵌套的decorator用法如下:
@log('execute') def now():print '2013-12-25'執(zhí)行結(jié)果如下:
>>> now() execute now(): 2013-12-25和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
now = log('execute')(now)我們來剖析上面的語句,首先執(zhí)行l(wèi)og('execute'),返回的是decorator函數(shù),再調(diào)用返回的函數(shù),參數(shù)是now函數(shù),返回值最終是wrapper函數(shù)。
?
14. 裝飾器的副作用
以上兩種decorator的定義都沒有問題,但還差最后一步。因?yàn)槲覀冎v了函數(shù)也是對(duì)象,它有__name__等屬性,但你去看經(jīng)過decorator裝飾之后的函數(shù),它們的__name__已經(jīng)從原來的'now'變成了'wrapper':
>>> now.__name__ 'wrapper'因?yàn)榉祷氐哪莻€(gè)wrapper()函數(shù)名字就是'wrapper',所以,需要把原始函數(shù)的__name__等屬性復(fù)制到wrapper()函數(shù)中,否則,有些依賴函數(shù)簽名的代碼執(zhí)行就會(huì)出錯(cuò)。
不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內(nèi)置的functools.wraps就是干這個(gè)事的,所以,一個(gè)完整的decorator的寫法如下:
import functoolsdef log(func):@functools.wraps(func)def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper或者針對(duì)帶參數(shù)的decorator:
import functoolsdef log(text):def decorator(func):@functools.wraps(func)def wrapper(*args, **kw):print '%s %s():' % (text, func.__name__)return func(*args, **kw)return wrapperreturn decoratorimport functools是導(dǎo)入functools模塊。模塊的概念稍候講解?,F(xiàn)在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。
當(dāng)然,即使是你用了functools的wraps,也不能完全消除這樣的副作用。你會(huì)發(fā)現(xiàn),即使是你你用了functools的wraps,你在用getargspec時(shí),參數(shù)也不見了。要修正這一問題,我們還得用Python的反射來解決,當(dāng)然,我相信大多數(shù)人的程序都不會(huì)去getargspec。所以,用functools的wraps應(yīng)該夠用了。
?
15. class式的 Decorator
首先,先得說一下,decorator的class方式,還是看個(gè)示例:
class myDecorator(object):def __init__(self, fn):print "inside myDecorator.__init__()"self.fn = fndef __call__(self):self.fn()print "inside myDecorator.__call__()"@myDecorator def aFunction():print "inside aFunction()"print "Finished decorating aFunction()"aFunction()# 輸出: # inside myDecorator.__init__() # Finished decorating aFunction() # inside aFunction() # inside myDecorator.__call__()1)一個(gè)是__init__(),這個(gè)方法是在我們給某個(gè)函數(shù)decorator時(shí)被調(diào)用,所以,需要有一個(gè)fn的參數(shù),也就是被decorator的函數(shù)。
2)一個(gè)是__call__(),這個(gè)方法是在我們調(diào)用被decorator函數(shù)時(shí)被調(diào)用的。
上面輸出可以看到整個(gè)程序的執(zhí)行順序。
這看上去要比“函數(shù)式”的方式更易讀一些。
上面這段代碼中,我們需要注意這幾點(diǎn):
1)如果decorator有參數(shù)的話,__init__() 成員就不能傳入fn了,而fn是在__call__的時(shí)候傳入的。
?
?
16. 一些decorator的示例
好了,現(xiàn)在我們來看一下各種decorator的例子:
?
16.1 給函數(shù)調(diào)用做緩存
這個(gè)例實(shí)在是太經(jīng)典了,整個(gè)網(wǎng)上都用這個(gè)例子做decorator的經(jīng)典范例,因?yàn)樘?jīng)典了,所以,我這篇文章也不能免俗。
from functools import wraps def memo(fn):cache = {}miss = object()@wraps(fn)def wrapper(*args):result = cache.get(args, miss)if result is miss:result = fn(*args)cache[args] = resultreturn resultreturn wrapper@memo def fib(n):if n < 2:return nreturn fib(n - 1) + fib(n - 2)上面這個(gè)例子中,是一個(gè)斐波拉契數(shù)例的遞歸算法。我們知道,這個(gè)遞歸是相當(dāng)沒有效率的,因?yàn)闀?huì)重復(fù)調(diào)用。比如:我們要計(jì)算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上來說,fib(3), fib(2), fib(1)在整個(gè)遞歸過程中被調(diào)用了兩次。
而我們用decorator,在調(diào)用函數(shù)前查詢一下緩存,如果沒有才調(diào)用了,有了就從緩存中返回值。一下子,這個(gè)遞歸從二叉樹式的遞歸成了線性的遞歸。
?
16.2 Profiler的例子
這個(gè)例子沒什么高深的,就是實(shí)用一些。
import cProfile, pstats, StringIOdef profiler(func):def wrapper(*args, **kwargs):datafn = func.__name__ + ".profile" # Name the data fileprof = cProfile.Profile()retval = prof.runcall(func, *args, **kwargs)#prof.dump_stats(datafn)s = StringIO.StringIO()sortby = 'cumulative'ps = pstats.Stats(prof, stream=s).sort_stats(sortby)ps.print_stats()print s.getvalue()return retvalreturn wrapper?
16.3 注冊(cè)回調(diào)函數(shù)
下面這個(gè)示例展示了通過URL的路由來調(diào)用相關(guān)注冊(cè)的函數(shù)示例:
class MyApp():def __init__(self):self.func_map = {}def register(self, name):def func_wrapper(func):self.func_map[name] = funcreturn funcreturn func_wrapperdef call_method(self, name=None):func = self.func_map.get(name, None)if func is None:raise Exception("No function registered against - " + str(name))return func()app = MyApp()@app.register('/') def main_page_func():return "This is the main page."@app.register('/next_page') def next_page_func():return "This is the next page."print app.call_method('/') print app.call_method('/next_page')注意:
1)上面這個(gè)示例中,用類的實(shí)例來做decorator。
2)decorator類中沒有__call__(),但是wrapper返回了原函數(shù)。所以,原函數(shù)沒有發(fā)生任何變化。
?
16.4 給函數(shù)打日志
下面這個(gè)示例演示了一個(gè)logger的decorator,這個(gè)decorator輸出了函數(shù)名,參數(shù),返回值,和運(yùn)行時(shí)間。
from functools import wraps def logger(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()print "function = {0}".format(fn.__name__)print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)print " time = %.6f sec" % (te-ts)return resultreturn wrapper@logger def multipy(x, y):return x * y@logger def sum_num(n):s = 0for i in xrange(n+1):s += ireturn sprint multipy(2, 10) print sum_num(100) print sum_num(10000000)上面那個(gè)打日志還是有點(diǎn)粗糙,讓我們看一個(gè)更好一點(diǎn)的(帶log level參數(shù)的):
import inspect def get_line_number():return inspect.currentframe().f_back.f_back.f_linenodef logger(loglevel):def log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()print "function = " + fn.__name__,print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)print " time = %.6f sec" % (te-ts)if (loglevel == 'debug'):print " called_from_line : " + str(get_line_number())return resultreturn wrapperreturn log_decorator但是,上面這個(gè)帶log level參數(shù)的有兩具不好的地方,
1) loglevel不是debug的時(shí)候,還是要計(jì)算函數(shù)調(diào)用的時(shí)間。
2) 不同level的要寫在一起,不易讀。
我們?cè)俳又倪M(jìn):
import inspectdef advance_logger(loglevel):def get_line_number():return inspect.currentframe().f_back.f_back.f_linenodef _basic_log(fn, result, *args, **kwargs):print "function = " + fn.__name__,print " arguments = {0} {1}".format(args, kwargs)print " return = {0}".format(result)def info_log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):result = fn(*args, **kwargs)_basic_log(fn, result, args, kwargs)return wrapperdef debug_log_decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):ts = time.time()result = fn(*args, **kwargs)te = time.time()_basic_log(fn, result, args, kwargs)print " time = %.6f sec" % (te-ts)print " called_from_line : " + str(get_line_number())return wrapperif loglevel is "debug":return debug_log_decoratorelse:return info_log_decorator你可以看到兩點(diǎn),
1)我們分了兩個(gè)log level,一個(gè)是info的,一個(gè)是debug的,然后我們?cè)谕馕哺鶕?jù)不同的參數(shù)返回不同的decorator。
2)我們把info和debug中的相同的代碼抽到了一個(gè)叫_basic_log的函數(shù)里,DRY原則。
?
16.5 一個(gè)MySQL的Decorator
下面這個(gè)decorator是我在工作中用到的代碼,我簡(jiǎn)化了一下,把DB連接池的代碼去掉了,這樣能簡(jiǎn)單點(diǎn),方便閱讀。
import umysql from functools import wrapsclass Configuraion:def __init__(self, env):if env == "Prod":self.host = "coolshell.cn"self.port = 3306self.db = "coolshell"self.user = "coolshell"self.passwd = "fuckgfw"elif env == "Test":self.host = 'localhost'self.port = 3300self.user = 'coolshell'self.db = 'coolshell'self.passwd = 'fuckgfw'def mysql(sql):_conf = Configuraion(env="Prod")def on_sql_error(err):print errsys.exit(-1)def handle_sql_result(rs):if rs.rows > 0:fieldnames = [f[0] for f in rs.fields]return [dict(zip(fieldnames, r)) for r in rs.rows]else:return []def decorator(fn):@wraps(fn)def wrapper(*args, **kwargs):mysqlconn = umysql.Connection()mysqlconn.settimeout(5)mysqlconn.connect(_conf.host, _conf.port, _conf.user, \_conf.passwd, _conf.db, True, 'utf8')try:rs = mysqlconn.query(sql, {})except umysql.Error as e:on_sql_error(e)data = handle_sql_result(rs)kwargs["data"] = dataresult = fn(*args, **kwargs)mysqlconn.close()return resultreturn wrapperreturn decorator@mysql(sql = "select * from coolshell" ) def get_coolshell(data):... ...... ..?
16.6 線程異步
下面量個(gè)非常簡(jiǎn)單的異步執(zhí)行的decorator,注意,異步處理并不簡(jiǎn)單,下面只是一個(gè)示例。
from threading import Thread from functools import wrapsdef async(func):@wraps(func)def async_func(*args, **kwargs):func_hl = Thread(target = func, args = args, kwargs = kwargs)func_hl.start()return func_hlreturn async_funcif __name__ == '__main__':from time import sleep@asyncdef print_somedata():print 'starting print_somedata'sleep(2)print 'print_somedata: 2 sec passed'sleep(2)print 'print_somedata: 2 sec passed'sleep(2)print 'finished print_somedata'def main():print_somedata()print 'back in main'print_somedata()print 'back in main'main()?
16.7?超時(shí)函數(shù)
這個(gè)函數(shù)的作用在于可以給任意可能會(huì)hang住的函數(shù)添加超時(shí)功能,這個(gè)功能在編寫外部API調(diào)用 、網(wǎng)絡(luò)爬蟲、數(shù)據(jù)庫查詢的時(shí)候特別有用。
timeout裝飾器的代碼如下:
# coding=utf-8 # 測(cè)試utf-8編碼 import sysreload(sys) sys.setdefaultencoding('utf-8')import signal, functoolsclass TimeoutError(Exception): passdef timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):def decorated(func):result = ""def _handle_timeout(signum, frame):global resultresult = error_messageraise TimeoutError(error_message)def wrapper(*args, **kwargs):global resultsignal.signal(signal.SIGALRM, _handle_timeout)signal.alarm(seconds)try:result = func(*args, **kwargs)finally:signal.alarm(0)return resultreturn resultreturn functools.wraps(func)(wrapper)return decorated@timeout(2) # 限定下面的slowfunc函數(shù)如果在5s內(nèi)不返回就強(qiáng)制拋TimeoutError Exception結(jié)束 def slowfunc(sleep_time):a = 1import timetime.sleep(sleep_time)return a# slowfunc(3) #sleep 3秒,正常返回 沒有異常print slowfunc(11) # 被終止?
16.8 Trace函數(shù)
有時(shí)候出于演示目的或者調(diào)試目的,我們需要程序運(yùn)行的時(shí)候打印出每一步的運(yùn)行順序 和調(diào)用邏輯。類似寫bash的時(shí)候的bash -x調(diào)試功能,然后Python解釋器并沒有 內(nèi)置這個(gè)時(shí)分有用的功能,那么我們就“自己動(dòng)手,豐衣足食”。
Trace裝飾器的代碼如下:
# coding=utf-8 # 測(cè)試utf-8編碼 import sys reload(sys) sys.setdefaultencoding('utf-8')import sys,os,linecache def trace(f):def globaltrace(frame, why, arg):if why == "call": return localtracereturn Nonedef localtrace(frame=1, why=2, arg=4):if why == "line":# record the file name and line number of every tracefilename = frame.f_code.co_filenamelineno = frame.f_linenobname = os.path.basename(filename)print "{}({}): {}".format( bname,lineno,linecache.getline(filename, lineno)),return localtracedef _f(*args, **kwds):sys.settrace(globaltrace)result = f(*args, **kwds)sys.settrace(None)return resultreturn _f@trace def xxx():a=1print aprint 22print 333xxx() #調(diào)用####################################### C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py t2.py(31): a=1 t2.py(32): print a 1 t2.py(33): print 22 22 t2.py(34): print 333 333Process finished with exit code 0?
16.9?單例模式
示例代碼:
# coding=utf-8 # 測(cè)試utf-8編碼 # 單例裝飾器 import sys reload(sys) sys.setdefaultencoding('utf-8')# 使用裝飾器實(shí)現(xiàn)簡(jiǎn)單的單例模式 def singleton(cls):instances = dict() # 初始為空def _singleton(*args, **kwargs):if cls not in instances: #如果不存在, 則創(chuàng)建并放入字典instances[cls] = cls(*args, **kwargs)return instances[cls]return _singleton@singleton class Test(object):pass if __name__ == '__main__':t1 = Test()t2 = Test()# 兩者具有相同的地址print t1print t2?
16.10?LRUCache
下面要分享的這個(gè)LRUCache不是我做的,是github上的一個(gè)庫,我們?cè)趯?shí)際環(huán)境中有用到。
先來說下這個(gè)概念,cache的意思就是緩存,LRU就是Least Recently Used,即最近最少使用,是一種內(nèi)存管理算法。總結(jié)來說這就是一種緩存方法,基于時(shí)間和容量。
一般在簡(jiǎn)單的python程序中,遇到需要處理緩存的情況時(shí)最簡(jiǎn)單的方式,聲明一個(gè)全局的dict就能解決(在python中應(yīng)盡量避免使用全局變量)。但是只是簡(jiǎn)單情況,這種情況會(huì)帶來的問題就是內(nèi)存泄漏,因?yàn)榭赡軙?huì)出現(xiàn)一直不命中的情況。
由此導(dǎo)致的一個(gè)需求就是,你要設(shè)定這個(gè)dict的最大容量,防止發(fā)生泄漏。但僅僅是設(shè)定最大容量是不夠的,設(shè)想當(dāng)你的dict變量已被占滿,還是沒有命中,該如何處理。
這時(shí)就需要加一個(gè)失效時(shí)間了。如果在指定失效期內(nèi)沒有使用到該緩存,則刪除。
綜述上面的需求和功能就是LRUCache干的事了。不過這份代碼做了更進(jìn)一層的封裝,可以讓你直接把緩存功能做為一個(gè)裝飾器來用。具體實(shí)現(xiàn)可以去參考代碼,別人實(shí)現(xiàn)之后看起來并不復(fù)雜 :)
from lru import lru_cache_function@lru_cache_function(max_size=1024, expiration=15*60) def f(x):print "Calling f(" + str(x) + ")"return xf(3) # This will print "Calling f(3)", will return 3 f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).代碼:?https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py
從python3.2開始內(nèi)置在functools了lru_cache的功能,說明這個(gè)需求是很普遍的。
?
?
17. 小結(jié)
在面向?qū)ο?#xff08;OOP)的設(shè)計(jì)模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實(shí)現(xiàn),而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數(shù)實(shí)現(xiàn),也可以用類實(shí)現(xiàn)。
decorator可以增強(qiáng)函數(shù)的功能,定義起來雖然有點(diǎn)復(fù)雜,但使用起來非常靈活和方便。
最后留個(gè)小作業(yè):
請(qǐng)編寫一個(gè)decorator,能在函數(shù)調(diào)用的前后打印出'begin call'和'end call'的日志。
再思考一下能否寫出一個(gè)@log的decorator,使它既支持:
@log def f():pass又支持:
@log('execute') def f():pass?
?
18. Refer:
[1]?12步輕松搞定python裝飾器
http://python.jobbole.com/81683/
[2]?裝飾器
liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000
[3]?Python修飾器的函數(shù)式編程
http://coolshell.cn/articles/11265.html
[4]?Python Decorator Library
https://wiki.python.org/moin/PythonDecoratorLibrary
[5]?Python裝飾器實(shí)例:調(diào)用參數(shù)合法性驗(yàn)證
http://python.jobbole.com/82114/
[6]?Python 裝飾器
http://python.jobbole.com/82344/
[7]?兩個(gè)實(shí)用的Python的裝飾器
http://blog.51reboot.com/%E4%B8%A4%E4%B8%AA%E5%AE%9E%E7%94%A8%E7%9A%84python%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8/
[8]?Python 中的閉包總結(jié)
http://dwz.cn/2CiO78
[9]?Python 的閉包和裝飾器
https://segmentfault.com/a/1190000004461404
[10]?Python修飾器的問題
https://segmentfault.com/q/1010000004595899
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Python 装饰器 函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++常用字符串分割方法
- 下一篇: Python集合(set)类型的操作