Python 装饰器 函数
?
Python裝飾器學習(九步入門):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
一些更加實用的 Python 裝飾器示例:https://sikasjc.github.io/2018/09/29/somedecorator/
Python裝飾器高級版—Python類內定義裝飾器并傳遞self參數:https://blog.51cto.com/yishi/2354752
?
?
Python 裝飾器學習(九步入門)
?
這是在 Python學習小組上介紹的內容,現學現賣、多練習是好的學習方式。
?
第一步:最簡單的函數,準備附加額外功能
示例代碼:
# -*- coding:gbk -*- '''示例1: 最簡單的函數,表示調用了兩次'''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()""" 結果: ***************************** before myfunc() called.myfunc() called. after myfunc() called. *****************************myfunc() called. """?
第二步:使用裝飾函數在函數執行前和執行后分別附加額外功能
示例代碼:
# -*- coding:gbk -*- '''示例2: 替換函數(裝飾) 裝飾函數的參數是被裝飾的函數對象,返回原函數對象 裝飾的實質語句: 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()""" 結果: before myfunc() called.myfunc() called. after myfunc() called. ****************************myfunc() called.myfunc() called. """?
第三步:使用語法糖@來裝飾函數
本例中 deco 沒有使用內嵌函數,可以看到第一次執行可以進入到裝飾函數,但是第二次不會進入裝飾函數
# -*- coding:gbk -*- '''示例3: 使用語法糖@來裝飾函數,相當于“myfunc = deco(myfunc)” 但發現新函數只在第一次被調用,且原函數多調用了一次'''def deco(func):print("before myfunc() called.")func()print("after myfunc() called.")return func@deco def myfunc():print("? ? myfunc() called.")# 第一次調用后,返回的是 deco 里面的 func 對象, # 所以第二次調用myfunc() 只輸出 myfunc() called. myfunc()? # 第一次調用 print('************************') myfunc()? # 第二次調用""" 結果: before myfunc() called.myfunc() called. after myfunc() called.myfunc() called. ************************myfunc() called. """?
第四步:使用 內嵌包裝函數 來確保 每次 新函數 都被調用(?被裝飾的函數沒有參數 )
裝飾函數 deco 返回內嵌包裝函數對象 _deco。使用內嵌包裝函數來確保每次新函數都被調用
# -*- coding:gbk -*- '''示例4: 使用內嵌包裝函數來確保每次新函數都被調用, 內嵌包裝函數的形參和返回值與原函數相同,裝飾函數返回內嵌包裝函數對象'''def deco(func):def _deco():print("before myfunc() called.")func()print("after myfunc() called.")# 不需要返回func,實際上應返回原函數的返回值return _deco@deco def myfunc():print("? ? myfunc() called.")return 'ok'myfunc() print('*************************') myfunc()""" 執行結果: before myfunc() called.myfunc() called. after myfunc() called. ************************* before myfunc() called.myfunc() called. after myfunc() called. """示例代碼:無參數的函數( 被裝飾的函數沒有參數 )
# 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()?
第五步:對?帶參數的函數?進行裝飾(?被裝飾的函數帶參數? )
示例代碼:
# -*- coding:gbk -*- '''示例5: 對帶參數的函數進行裝飾, 內嵌包裝函數的形參和返回值與原函數相同,裝飾函數返回內嵌包裝函數對象'''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)""" 執行結果: before myfunc() called.myfunc(1,2) called. after myfunc() called. result: 3 ************************* before myfunc() called.myfunc(3,4) called. after myfunc() called. result: 7 """示例代碼:被裝飾的函數帶參數?
# 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)?
第六步:對 參數數量不確定 的函數進行裝飾
示例代碼:
# -*- coding:gbk -*- '''示例6: 對參數數量不確定的函數進行裝飾, 參數用(*args, **kwargs),自動適應變參和命名參數'''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)""" 執行結果: ****************************** 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 """?
第七步:讓 裝飾器 帶 參數
示例代碼:
# -*- coding:gbk -*- '''示例7: 在示例4的基礎上,讓裝飾器帶參數, 和上一示例相比在外層多了一層包裝。 裝飾函數名實際上應更有意義些'''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()""" 執行結果: ************************************ before myfunc called [mymodule].myfunc_1() called. after myfunc called [mymodule]. ************************************ before myfunc2 called [module2].myfunc_2() called. after myfunc2 called [module2]. """裝飾器帶參數,在原有裝飾器的基礎上,設置外部變量
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()?
第八步:讓 裝飾器 帶 類 參數
示例代碼:
# -*- coding:gbk -*- '''示例8: 裝飾器帶類參數'''class Locker:def __init__(self):print("locker.__init__() should be not called.")@staticmethoddef acquire():print("locker.acquire() called.(這是靜態方法)")@staticmethoddef release():print("locker.release() called.(不需要對象實例)")def deco(cls):'''cls 必須實現acquire和release靜態方法'''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()""" 執行結果: ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態方法)myfunc() called. locker.release() called.(不需要對象實例) ********************************************* before myfunc called [<class '__main__.Locker'>]. locker.acquire() called.(這是靜態方法)myfunc() called. locker.release() called.(不需要對象實例) """裝飾器 和 閉包 混用:
# 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字符串的同時,顯示原有對象類型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!")?
第九步:裝飾器帶類參數,并分拆公共類到其他py文件中,
同時 演示了對一個函數應用多個裝飾器
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 必須實現acquire和release靜態方法"""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測試代碼:
# -*- coding:gbk -*-""" 示例 9: 裝飾器帶類參數,并分拆公共類到其他py文件中 同時演示了對一個函數應用多個裝飾器 """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))""" 執行結果: 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 """?
下面是參考資料,當初有不少地方沒看明白,真正練習后才明白些:
1. Python裝飾器學習: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 種類型
?
- 函數? 裝飾? 函數
- 函數? 裝飾? 類
- 類? 裝飾? 函數
- 類? 裝飾? 類
?
@wraps(func) 要使用這個必須導入functools,這個作用是消除(被裝飾后的函數名等屬性的改變)副作用
參考: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?
一:函數 裝飾 函數
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))?
裝飾不帶參數的函數
# -*- 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()#備注:@是語法糖 # 不用語法糖的情況下,使用下面語句也能實現裝飾作用:把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)?
裝飾帶一個參數的函數
# -*- coding: utf-8 -*-def clothes(func):def wear(anything): # 實際是定義一個anything參數,對應body函數參數print('Buy clothes!{}'.format(func.__name__))return func(anything) # 執行func函數,并傳入調用傳入的anything參數# wear = func(anything) # 在這一步,實際上可以看出來,為啥wear必須帶參數,因為它就是func(anything)return wear# 所以clothes的結果是# clothes = wear = func(anything)# 不用語法糖的情況下就是# body = clothes(body)('hands')# 進一步證明:print(body.__name__) 顯示的是wear函數@clothes def body(part):print('The body feels could!{}'.format(part))body('hands')?
裝飾帶不定長參數的函數
通常裝飾器不只裝飾一個函數,每個函數參數的個數也不相同,這個時候使用不定長參數*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')?
裝飾器帶參數
# 把裝飾器再包裝,實現了seasons傳遞裝飾器參數。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)?
二:函數 裝飾 類
示例:
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()示例:
?
?
創建單例(Singletons)
單例是一個只有一個實例的類。Python中有幾種常用的單例,包括None、True和False。事實上,None是一個單例,允許你使用is關鍵字比較None。
示例:下面的?@singleton?裝飾器將類的第一個實例存儲為屬性,從而將類轉換為單例對象。稍后創建實例的嘗試只是返回存儲的實例:
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如上所示,這個類裝飾器遵循與我們的函數裝飾器相同的模板。唯一的區別是,我們使用cls作為參數名來表示它是一個類裝飾器,而不是func。
運行效果:
>>> first_one = TheOne() >>> another_one = TheOne()>>> id(first_one) 140094218762280>>> id(another_one) 140094218762280>>> first_one is another_one True很明顯,first_one確實與another_one完全相同。
在Python中,單例類的使用并不像在其他語言中那樣頻繁。單例的效果通常在模塊中作為全局變量更好地實現。
?
三:類 裝飾 函數、方法
?
類 裝飾 函數
定義一個類裝飾器,裝飾函數,默認調用 __call__ 方法
class Decorator(object):def __init__(self, func): # 傳送的是test方法self.func = funcdef __call__(self, *args, **kwargs): # 接受任意參數print('函數調用CALL')return self.func(*args, **kwargs) # 適應test的任意參數 @Decorator # 如果帶參數,init中的func是此參數。 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'))無參數
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)有參數
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)?
類 裝飾 方法
????定義一個類裝飾器,裝飾類中的函數,默認調用__get__方法
????實際上把類方法變成屬性了,還記得類屬性裝飾器吧,@property
????下面自已做一個property
class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):"""instance:代表實例,sum中的selfowner:代表類本身,Test類"""print('調用的是get函數')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) # 眾所周知,屬性是不加括號的,sum真的變成了屬性示例:
做一個求和屬性sum,統計所有輸入的數字的和
class Decorator(object):def __init__(self, func):self.func = funcdef __get__(self, instance, owner):print('調用的是get函數')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)無參數
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)有參數
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()?
函數 裝飾 類中的方法
示例代碼 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運行結果:
[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 的裝飾器裝飾一個類的方法,同時在裝飾器函數中調用類里面的其他方法。這里以捕獲一個方法的異常為例來進行說明。
有一個類Test, 它的結構如下:
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.在類中有一個方法read_value(),這個方法在多個地方被調用。由于某些原因,方法read_value有可能隨機拋出Exception導致程序崩潰。所以需要對整個方法做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。
使用裝飾器來解決這個問題,裝飾器函數應該寫在類里面還是類外面呢?答案是,寫在類外面。那么既然寫在類外面,如何調用這個類的其他方法呢?
首先寫出一個最常見的處理異常的裝飾器:
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.這種寫法,確實可以捕獲到?origin_func()的異常,但是如果在發生異常的時候,需要調用類里面的另一個方法來處理異常,這又應該怎么辦?答案是給 wrapper 增加一個參數:self.
代碼變為如下形式:
def catch_exception(origin_func):def wrapper(self, *args, **kwargs):try:u = origin_func(self, *args, **kwargs)return uexcept Exception:self.revive() #不用顧慮,直接調用原來的類的方法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.執行結果:
?
示例代碼 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()?
?
類中的方法 裝飾 方法 和 函數
- 方法:類中的成員函數叫做 方法
- 函數:不在類中的函數,即普通函數叫做 函數
?
類中的方法 裝飾 函數
在類里面定義個函數,用來裝飾其它函數,嚴格意義上說不屬于類裝飾器。
class Buy(object):def __init__(self, func):self.func = func# 在類里定義一個函數def clothes(func): # 這里不能用self,因為接收的是body函數# 其它都和普通的函數裝飾器相同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 # 定義一個類屬性,稍后在裝飾器里更改self.func = True# 在類里定義一個裝飾器def clothes(func): # func接收bodydef ware(self, *args, **kwargs): # self,接收body里的self,也就是類實例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() # 實例化類 b.body() # 運行body print(b.func) # 查看更改后的self.func值,是False,說明修改完成?
?
?
?
?
前置知識
?
閉 包
?
javascript 閉包:https://www.runoob.com/js/js-function-closures.html
? ? ? ? 閉包是一種保護私有變量的機制,在函數執行時形成私有的作用域,保護里面的私有變量不受外界干擾。直觀的說:就是形成一個不銷毀的棧環境。
? ? ? ? 閉包在維基百科上的定義如下:?在計算機科學中,閉包(Closure)是詞法閉包(Lexical?Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法:認為閉包是由函數和與其相關的引用環境組合而成的實體。??
下面給出一個使用閉包實現的?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")運行結果:
Prefix: msg msg在上面這個閉包的例子中,prefix?變量時所謂的自由變量,其在?return logger?執行完畢后,便脫離了創建它的環境logger_factory,但因為其被logger_factory?中定義的?logger?函數所引用,其生命周期將至少和?logger?函數相同。這樣,在?logger?中就可以引用到logger_factory?作用域內的變量?prefix。
將 閉包 與 namespace 結合起來 示例:
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()()運行結果如下:
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在這個例子中,當?fun_outer?被定義時,其內部的定義的?fun_inner?函數對?print "fun_inner: " + var?中所引用的?var?變量進行搜索,發現第一個被搜索到的?var?定義在?fun_outer?的?local namespace?中,因此使用此定義,通過?print "fun_outer: " + str(id(var))?和?print "fun_inner: " + str(id(var)),當var?超出?fun_outer?的作用域后,依然存活,而?fun_outer?中的unused_var?變量由于沒有被?fun_inner?所引用,因此會被?GC。
?
?
什么是閉包?
?
內部函數對外部函數作用域里變量的引用(非全局變量),則稱內部函數為閉包。
簡單說,閉包就是根據不同的配置信息得到不同的結果
再來看看專業的解釋:閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。
?
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))# 運行結果: # 123 # 144分析一下:
我們發現?make_adder?是一個函數,包括一個參數?arg_1 ,比較特殊的地方是這個函數里面又定義了一個新函數,這個新函數里面的一個變量正好是外部?make_adder?的參數。也就是說,外部傳遞過來的?arg_1 參數已經和?adder?函數綁定到一起了,形成了一個新函數,我們可以把??arg_1?看做新函數的一個配置信息,配置信息不同,函數的功能就不一樣了,也就是能得到定制之后的函數.
再看看運行結果,我們發現,雖然 p 和 q 都是?make_adder?生成的,但是因為配置參數不同,后面再執行相同參數的函數后得到了不同的結果。這就是閉包。
?
示例 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()# 執行結果 # Hello, king , 1 access! # Hello, king , 2 access! # Hello, king , 3 access!分析一下
這個程序比較有趣,我們可以把這個程序看做統計一個函數調用次數的函數。count[0]可以看做一個計數器,每執行一次?hello?函數,count[0]?的值就加 1。也許你會有疑問:為什么不直接寫?count?而用一個列表? 這是 python2 的一個bug,如果不用列表的話,會報這樣一個錯誤:UnboundLocalError: local variable 'count' referenced before assignment.
什么意思? 就是說?conut?這個變量你沒有定義就直接引用了,我不知道這是個什么東西,程序就崩潰了。于是在 python3 里面引入了一個關鍵字:nonlocal,這個關鍵字是干什么的? 就是告訴 python 程序,我的這個?count?變量是在外部定義的,你去外面找吧。然后 python 就去外層函數找,然后就找到了?count=0?這個定義和賦值,程序就能正常執行了。
?
示例 2 改進:
def hello_counter(name):count = 0def counter():nonlocal countcount += 1print('Hello,', name, ',', str(count) + ' access!')return counterhello = hello_counter('king') hello() hello() hello()關于這個問題的研究您可以參考: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())# 執行結果 # <b><i>hello world</i></b>簡單分析
怎么樣? 這個程序熟悉嗎? 這不是傳說的的裝飾器嗎? 對,這就是裝飾器,其實,裝飾器就是一種閉包,
我們再回想一下裝飾器的概念:對函數(參數,返回值 等)進行加工處理,生成一個功能增強版的一個函數。
再看看閉包的概念,這個增強版的函數不就是我們配置之后的函數嗎 ?
區別在于,裝飾器的參數是一個 函數 或 類 ,專門對 類 或 函數 進行加工處理。
Python 里面的好多高級功能,比如裝飾器,生成器,列表推到,閉包,匿名函數等。
?
?
?
探索 裝飾器
定義
?
基本語法
語法糖
@bar def foo():print("foo")其等價于:
def foo(): print("foo") foo = bar(foo)?
無參數 裝飾器
def foo(func):print('decorator foo')return func@foo def bar():print('bar')bar()foo 函數被用作裝飾器,其本身接收一個函數對象作為參數,然后做一些工作后,返回接收的參數,供外界調用。
注意:時刻牢記?@foo?只是一個語法糖,其本質是?foo = bar(foo)
?
帶參數 的 裝飾器
示例代碼:
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)上述代碼想要實現一個性能分析器,并接收一個參數,來控制性能分析器是否生效,其運行效果如下所示:
add result: 2 used time: 3 mul result: 10上述代碼中裝飾器的調用等價于:
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))?
?
類 的 裝飾器
簡單示例:
def bar(dummy):print('bar')def inject(cls):cls.bar = barreturn cls@inject class Foo(object):passfoo = Foo() foo.bar()上述代碼的?inject?裝飾器為類動態的添加一個?bar?方法,因為類在調用非靜態方法的時候會傳進一個self?指針,因此?bar?的第一個參數我們簡單的忽略即可,
運行結果如下:bar
?
?
類 裝飾器
類裝飾器 相比 函數裝飾器,具有靈活度大,高內聚、封裝性等優點。其實現起來主要是靠類內部的?__call__?方法,當使用?@?形式將裝飾器附加到函數上時,就會調用此方法,下面時一個實例:
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()運行結果如下:
class decorator bar?
?
內置裝飾器
Python 中內置的裝飾器有三個:?staticmethod、classmethod?和?property
?
staticmethod?是類靜態方法,其跟成員方法的區別是沒有?self?指針,并且可以在類不進行實例化的情況下調用,
下面是一個實例,對比靜態方法 和 成員方法:
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?與成員方法的區別在于所接收的第一個參數不是?self?類實例的指針,而是當前類的具體類型,
下面是一個實例:
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()運行結果如下:
<class '__main__.Foo'> <__main__.Foo object at 0x000002895A412508>?
property?是屬性的意思,即可以通過通過類實例直接訪問的信息,下面是具體的例子:
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?裝飾器所裝飾的成員函數去掉,則?Foo.var?屬性為只讀屬性,使用?foo.var = 'var 2'?進行賦值時會拋出異常,其運行結果如下:
var 1 var 2?
?
調用順序
裝飾器的調用順序與使用?@?語法糖聲明的順序相反,如下所示:
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()其等價于:
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()通過等價的調用形式我們可以看到,按照 python 的函數求值序列,decorator_b(fun)?會首先被求值,然后將其結果作為輸入,傳遞給?decorator_a,因此其調用順序與聲明順序相反。
其運行結果如下所示:
decorator_b decorator_a foo?
?
調用時機
裝飾器很好用,那么它什么時候被調用?性能開銷怎么樣?會不會有副作用?接下來我們就以幾個實例來驗證我們的猜想。
首先我們驗證一下裝飾器的性能開銷,代碼如下所示:
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)))運行結果如下:
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?
在運行結果中:
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證實了在相同?.py?文件中,裝飾器對所裝飾的函數只進行一次裝飾,不會每次調用相應函數時都重新裝飾,這個很容易理解,因為其本質等價于下面的函數簽名重新綁定:
foo = decorator_a(decorator_b(foo))?
對于跨模塊的調用,我們編寫如下結構的測試代碼:
.?? ├──?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()上述代碼通過創建?common.mod_a?和?common.mod_b?兩個子模塊,并調用?common.decorator?中的?foo?函數,來測試跨模塊時裝飾器的工作情況,運行?test.py?的結果如下所示:
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經過上面的驗證,可以看出,對于跨模塊的調用,裝飾器也只會初始化一次,不過這要歸功于?*.pyc,這與本文主題無關,故不詳述。
?
?
裝飾器副作用
關于裝飾器副作用的話題比較大,這不僅僅是裝飾器本身的問題,更多的時候是我們設計上的問題,
下面給出一個初學裝飾器時大家都會遇到的一個問題 —— 丟失函數元信息:
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())運行結果:
foo.__module__: __main__ foo.__name__: inner foo.__doc__: None foo result可以看到,在使用 decorator_a 對 foo 函數進行裝飾后,foo 的元信息會丟失,解決方案參見:?functools.wraps
?
?
多個裝飾器運行期行為
前面已經講解過裝飾器的調用順序和調用時機,但是被多個裝飾器裝飾的函數,其運行期行為還是有一些細節需要說明的,而且很可能其行為會讓你感到驚訝,下面時一個實例:
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")大家先來看一下運行結果,看看是不是跟自己想象中的一致:
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首先,裝飾器初始化時的調用順序與我們前面講解的一致,如下:
Declaring login_a [TRACE] login_debug_helper [TRACE] proxy_fun [TRACE] logger然而,接下來,來自?logger?裝飾器中的?inner?函數首先被執行,然后才是login_debug_helper?返回的?proxy_fun?中的?delegate_fun?函數。各位讀者發現了嗎,運行期執行login_a?函數的時候,裝飾器中返回的函數的執行順序是相反的,難道是我們前面講解的例子有錯誤嗎?其實,如果大家的認為運行期調用順序應該與裝飾器初始化階段的順序一致的話,那說明大家沒有看透這段代碼的調用流程,下面我來為大家分析一下。
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當裝飾器?login_debug_helper?被調用時,其等價于:
gin_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')對于只有?login_debug_helper?的情況,現在就應該是執行完?login_a?輸出結果的時刻了,但是如果現在在加上?logger?裝飾器的話,那么這個?login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')?就被延遲執行,而將?login_debug_helper(show_debug_info=True)(login_a)?作為參數傳遞給?logger,我們令?login_tmp = login_debug_helper(show_debug_info=True)(login_a),則調用過程等價于:
login_tmp = login_debug_helper(show_debug_info=True)(login_a) login_a = logger(login_tmp) login_a('mdl', 'pwd')相信大家看過上面的等價變換后,已經明白問題出在哪里了,如果你還沒有明白,我強烈建議你把這個例子自己敲一遍,并嘗試用自己的方式進行化簡,逐步得出結論。
?
一些實例參考
本文主要講解原理性的東西,具體的實例可以參考下面的鏈接:
Python裝飾器實例:調用參數合法性驗證
Python裝飾器與面向切面編程
Python裝飾器小結
Python tips: 超時裝飾器, @timeout decorator
python中判斷一個運行時間過長的函數
python利用裝飾器和threading實現異步調用
python輸出指定函數運行時間的裝飾器
python通過裝飾器和線程限制函數的執行時間
python裝飾器的一個妙用
通過 Python 裝飾器實現DRY(不重復代碼)原則
參考資料
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裝飾器學習(九步入門)
python 裝飾器和 functools 模塊
?
?
?
理解 Python 中的裝飾器
?
From:https://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html
?
文章先由 stackoverflow上面的一個問題引起吧,如果使用如下的代碼:
@makebold @makeitalic def say():return "Hello"打印出如下的輸出:
<b><i>Hello<i></b>你會怎么做?最后給出的答案是:
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()) # 結果: <b><i>hello world</i></b>現在我們來看看如何從一些最基礎的方式來理解Python的裝飾器。英文討論參考Here。
裝飾器是一個很著名的設計模式,經常被用于有切面需求的場景,較為經典的有插入日志、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼并繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。
?
1.1. 需求是怎么來的 ?
裝飾器的定義很是抽象,我們來看一個小例子。
def foo():print('in foo()')foo()這是一個很無聊的函數沒錯。但是突然有一個更無聊的人,我們稱呼他為B君,說我想看看執行這個函數用了多長時間,好吧,那么我們可以這樣做:
import timedef foo():start = time.clock()print('in foo()') end = time.clock()print('used:', end - start) foo()很好,功能看起來無懈可擊。可是蛋疼的B君此刻突然不想看這個函數了,他對另一個叫foo2的函數產生了更濃厚的興趣。
怎么辦呢?如果把以上新增加的代碼復制到foo2里,這就犯了大忌了~復制什么的難道不是最討厭了么!而且,如果B君繼續看了其他的函數呢?
?
1.2. 以不變應萬變,是變也
還記得嗎,函數在Python中是一等公民,那么我們可以考慮重新定義一個函數timeit,將foo的引用傳遞給他,然后在timeit中調用foo并進行計時,這樣,我們就達到了不改動foo定義的目的,而且,不論B君看了多少個函數,我們都不用去修改函數定義了!
import timedef foo():print('in foo()') def timeit(func):start = time.clock()func()end = time.clock()print('used:', end - start) timeit(foo)看起來邏輯上并沒有問題,一切都很美好并且運作正常!……等等,我們似乎修改了調用部分的代碼。原本我們是這樣調用的:foo(),修改以后變成了:timeit(foo)。這樣的話,如果foo在N處都被調用了,你就不得不去修改這N處的代碼。或者更極端的,考慮其中某處調用的代碼無法修改這個情況,比如:這個函數是你交給別人使用的。
?
1.3. 最大限度地少改動!
既然如此,我們就來想想辦法不修改調用的代碼;如果不修改調用代碼,也就意味著調用foo()需要產生調用timeit(foo)的效果。我們可以想到將timeit賦值給foo,但是timeit似乎帶有一個參數……想辦法把參數統一吧!如果timeit(foo)不是直接產生調用效果,而是返回一個與foo參數列表一致的函數的話……就很好辦了,將timeit(foo)的返回值賦值給foo,然后,調用foo()的代碼完全不用修改!
# -*- coding: UTF-8 -*- import timedef foo():print('in foo()')# 定義一個計時器,傳入一個,并返回另一個附加了計時功能的方法 def timeit(func):# 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝def wrapper():start = time.perf_counter()func()end = time.perf_counter()print('used:', end - start)# 將包裝后的函數返回return wrapperfoo = timeit(foo) foo()這樣,一個簡易的計時器就做好了!我們只需要在定義foo以后調用foo之前,加上foo = timeit(foo),就可以達到計時的目的,這也就是裝飾器的概念,看起來像是foo被timeit裝飾了。在在這個例子中,函數進入和退出時需要計時,這被稱為一個橫切面(Aspect),這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。與傳統編程習慣的從上往下執行方式相比較而言,像是在函數執行的流程中橫向地插入了一段邏輯。在特定的業務領域里,能減少大量重復代碼。面向切面編程還有相當多的術語,這里就不多做介紹,感興趣的話可以去找找相關的資料。
這個例子僅用于演示,并沒有考慮foo帶有參數和有返回值的情況,完善它的重任就交給你了 :)
上面這段代碼看起來似乎已經不能再精簡了,Python于是提供了一個語法糖來降低字符輸入量。
重點關注第11行的@timeit,在定義上加上這一行與另外寫foo = timeit(foo)完全等價
千萬不要以為@有另外的魔力。除了字符輸入少了一些,還有一個額外的好處:這樣看上去更有裝飾器的感覺。
-------------------
?
Python中函數也是被視為對象(Python 中一切 皆 對象)
要理解python的裝飾器,我們首先必須明白在Python中函數也是被視為對象。這一點很重要。先看一個例子:
def shout(word="yes"):return word.capitalize() + " !"print(shout()) # 輸出 : 'Yes !'# 作為一個對象,你可以把函數賦給任何其他對象變量 scream = shout# 注意我們沒有使用圓括號,因為我們不是在調用函數 # 我們把函數shout賦給scream,也就是說你可以通過scream調用shout print(scream()) # 輸出 : 'Yes !'# 還有,你可以刪除舊的名字shout,但是你仍然可以通過scream來訪問該函數 del shout try:print(shout()) except BaseException as e:print(e)# 輸出 : "name 'shout' is not defined"print(scream()) # 輸出 : 'Yes !'我們暫且把這個話題放旁邊,我們先看看 python 另外一個很有意思的屬性:可以在函數中定義函數:
def talk():# 你可以在talk中定義另外一個函數def whisper(word="yes"):return word.lower() + "..."# ... 并且立馬使用它print(whisper())# 你每次調用'talk',定義在talk里面的whisper同樣也會被調用 talk() # 輸出 : # yes...# 但是"whisper" 不會單獨存在: try:print(whisper()) except BaseException as e:print(e)# 輸出 : "name 'whisper' is not defined"*?
函數引用
從以上兩個例子我們可以得出,函數既然作為一個對象,因此:
- 1. 其可以被賦給其他變量
- 2. 其可以被定義在另外一個函數內
這也就是說,函數可以返回一個函數,看下面的例子:
def get_talk(type="shout"):# 我們定義另外一個函數def shout(word="yes"):return word.capitalize() + " !"def whisper(word="yes"):return word.lower() + "..."# 然后我們返回其中一個if type == "shout":# 我們沒有使用(),因為我們不是在調用該函數# 我們是在返回該函數return shoutelse:return whisper# 然后怎么使用呢 ? # 把該函數賦予某個變量 talk = get_talk()# 這里你可以看到talk其實是一個函數對象: print(talk) # 輸出 : <function shout at 0xb7ea817c># 該對象由函數返回的其中一個對象: print(talk())# 或者你可以直接如下調用 : print(get_talk("whisper")()) # 輸出 : yes...還有,既然可以返回一個函數,我們可以把它作為參數傳遞給函數:
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 !這里你已經足夠能理解裝飾器了,其他它可被視為封裝器。也就是說,它能夠讓你在裝飾前后執行代碼而無須改變函數本身內容。
?
?
手工裝飾
那么如何進行手動裝飾呢?
# 裝飾器是一個函數,而其參數為另外一個函數 def my_shiny_new_decorator(a_function_to_decorate):# 在內部定義了另外一個函數:一個封裝器。# 這個函數將原始函數進行封裝,所以你可以在它之前或者之后執行一些代碼def the_wrapper_around_the_original_function():# 放一些你希望在真正函數執行前的一些代碼print("Before the function runs")# 執行原始函數a_function_to_decorate()# 放一些你希望在原始函數執行后的一些代碼print("After the function runs")# 在此刻,"a_function_to_decrorate"還沒有被執行,我們返回了創建的封裝函數# 封裝器包含了函數以及其前后執行的代碼,其已經準備完畢return the_wrapper_around_the_original_function# 現在想象下,你創建了一個你永遠也不遠再次接觸的函數 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# 好了,你可以封裝它實現行為的擴展。可以簡單的把它丟給裝飾器 # 裝飾器將動態地把它和你要的代碼封裝起來,并且返回一個新的可用的函數。 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# 現在你也許要求當每次調用a_stand_alone_function時, # 實際調用卻是a_stand_alone_function_decorated。 # 實現也很簡單,可以用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當然你也可以累積裝飾:
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 用于轉換為粗體 def make_bold(fn):# 結果返回該函數def wrapper():# 插入一些執行前后的代碼return "<b>" + fn() + "</b>"return wrapper# 裝飾器 make_italic 用于轉換為斜體 def make_italic(fn):# 結果返回該函數def wrapper():# 插入一些執行前后的代碼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>?
?
內置的裝飾器
內置的裝飾器有三個,分別是staticmethod、classmethod和property,作用分別是把類中定義的實例方法變成靜態方法、類方法和類屬性。由于模塊里可以定義函數,所以靜態方法和類方法的用處并不是太多,除非你想要完全的面向對象編程。而屬性也不是不可或缺的,Java沒有屬性也一樣活得很滋潤。從我個人的Python經驗來看,我沒有使用過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這里定義的屬性是一個只讀屬性,如果需要可寫,則需要再定義一個setter:
@name.setter
def name(self, name):
? ? self._name = name
functools模塊
functools模塊提供了兩個裝飾器。這個模塊是Python 2.5后新增的,一般來說大家用的應該都高于這個版本。但我平時的工作環境是2.4 T-T
2.3.1. wraps(wrapped[, assigned][, updated]):?
這是一個很有用的裝飾器。看過前一篇反射的朋友應該知道,函數是有幾個特殊屬性比如函數名,在被裝飾后,上例中的函數名foo會變成包裝函數的名字wrapper,如果你希望使用反射,可能會導致意外的結果。這個裝飾器可以解決這個問題,它能將裝飾過的函數的特殊屬性保留。
首先注意第5行,如果注釋這一行,foo.__name__將是'wrapper'。另外相信你也注意到了,這個裝飾器竟然帶有一個參數。實際上,他還有另外兩個可選的參數,assigned中的屬性名將使用賦值的方式替換,而updated中的屬性名將使用update的方式合并,你可以通過查看functools的源代碼獲得它們的默認值。對于這個裝飾器,相當于wrapper = functools.wraps(func)(wrapper)。
?
?
?
深入淺出 Python 裝飾器:16 步輕松搞定 Python 裝飾器
?
而Python使用了一種相對于Decorator Pattern和Annotation來說非常優雅的方法,這種方法不需要你去掌握什么復雜的OO模型或是Annotation的各種類庫規定,完全就是語言層面的玩法:一種函數式編程的技巧。如果你看過本站的《函數式編程》,你一定會為函數式編程的那種“描述你想干什么,而不是描述你要怎么去實現”的編程方式感到暢快。(如果你不了解函數式編程,那在讀本文之前,還請你移步去看看《函數式編程》) 好了。
?
1. 函數
在python中,函數通過def關鍵字、函數名和可選的參數列表定義。通過return關鍵字返回值。我們舉例來說明如何定義和調用一個簡單的函數:
def foo():return 1 foo() 1方法體(當然多行也是一樣的)是必須的,通過縮進來表示,在方法名的后面加上雙括號()就能夠調用函數
?
2. 作用域
python中,函數會創建一個新的作用域。python開發者可能會說函數有自己的命名空間,差不多一個意思。這意味著在函數內部碰到一個變量的時候函數會優先在自己的命名空間里面去尋找。讓我們寫一個簡單的函數看一下 本地作用域 和 全局作用域有什么不同:
a_string = "This is a global variable" def foo():print locals() print globals() {..., 'a_string': 'This is a global variable'} foo() # 2 {}內置的函數globals返回一個包含所有python解釋器知道的變量名稱的字典(為了干凈和洗的白白的,我省略了python自行創建的一些變量)。在#2我調用了函數 foo 把函數內部本地作用域里面的內容打印出來。我們能夠看到,函數foo有自己獨立的命名空間,雖然暫時命名空間里面什么都還沒有。
?
3. 變量解析規則
當然這并不是說我們在函數里面就不能訪問外面的全局變量。在python的作用域規則里面,創建變量一定會在當前作用域里創建一個變量,但是訪問或者修改變量時會先在當前作用域查找變量,沒有找到匹配變量的話會依次向上在閉合的作用域里面進行查看找。所以如果我們修改函數foo的實現讓它打印全局的作用域里的變量也是可以的:
a_string = "This is a global variable" def foo():print a_string # 1 foo() This is a global variable在#1處,python解釋器會嘗試查找變量a_string,當然在函數的本地作用域里面找不到,所以接著會去上層的作用域里面去查找。
但是另一方面,假如我們在函數內部給全局變量賦值,結果卻和我們想的不一樣:
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'我們能夠看到,全局變量能夠被訪問到(如果是可變數據類型(像list,dict這些)甚至能夠被更改)但是賦值不行。在函數內部的#1處,我們實際上新創建了一個局部變量,隱藏全局作用域中的同名變量。我們可以通過打印出局部命名空間中的內容得出這個結論。我們也能看到在#2處打印出來的變量a_string的值并沒有改變。
?
4. 變量生存周期
值得注意的一個點是,變量不僅是生存在一個個的命名空間內,他們都有自己的生存周期,請看下面這個例子:
def foo():x = 1 foo() print x # 1 #Traceback (most recent call last): #NameError: name 'x' is not defined#1處發生的錯誤不僅僅是因為作用域規則導致的(盡管這是拋出了NameError的錯誤的原因)它還和python以及其它很多編程語言中函數調用實現的機制有關。在這個地方這個執行時間點并沒有什么有效的語法讓我們能夠獲取變量x的值,因為它這個時候壓根不存在!函數foo的命名空間隨著函數調用開始而開始,結束而銷毀。
?
5. 函數參數
python允許我們向函數傳遞參數,參數會變成本地變量存在于函數內部。
def foo(x):print locals() foo(1) {'x': 1}在Python里有很多的方式來定義和傳遞參數,完整版可以查看 python官方文檔。我們這里簡略的說明一下:函數的參數可以是必須的位置參數或者是可選的命名,默認參數。
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處我們定義了函數foo,它有一個位置參數x和一個命名參數y。在#2處我們能夠通過常規的方式來調用函數,盡管有一個命名參數,但參數依然可以通過位置傳遞給函數。在調用函數的時候,對于命名參數y我們也可以完全不管就像#3處所示的一樣。如果命名參數沒有接收到任何值的話,python會自動使用聲明的默認值也就是0。需要注意的是我們不能省略第一個位置參數x, 否則的話就會像#4處所示發生錯誤。
目前還算簡潔清晰吧, 但是接下來可能會有點令人困惑。python支持函數調用時的命名參數(個人覺得應該是命名實參)。看看#5處的函數調用,我們傳遞的是兩個命名實參,這個時候因為有名稱標識,參數傳遞的順序也就不用在意了。
當然相反的情況也是正確的:函數的第二個形參是y,但是我們通過位置的方式傳遞值給它。在#2處的函數調用foo(3,1),我們把3傳遞給了第一個參數,把1傳遞給了第二個參數,盡管第二個參數是一個命名參數。
一個簡單的概念:函數的參數可以有名稱和位置。這意味著在函數的定義和調用的時候會稍稍在理解上有點兒不同。我們可以給只定義了位置參數的函數傳遞命名參數(實參),反之亦然!如果覺得不夠可以查看官方文檔
?
6. 嵌套函數
Python允許創建嵌套函數。這意味著我們可以在函數里面定義函數而且現有的作用域和變量生存周期依舊適用。
def outer():x = 1def inner():print x # 1return inner() # 2 outer() 1這個例子有一點兒復雜,但是看起來也還行。想一想在#1發生了什么:python解釋器需找一個叫x的本地變量,查找失敗之后會繼續在上層的作用域里面尋找,這個上層的作用域定義在另外一個函數里面。對函數outer來說,變量x是一個本地變量,但是如先前提到的一樣,函數inner可以訪問封閉的作用域(至少可以讀和修改)。在#2處,我們調用函數inner,非常重要的一點是,inner也僅僅是一個遵循python變量解析規則的變量名,python解釋器會優先在outer的作用域里面對變量名inner查找匹配的變量.
?
7. 函數是 python 世界里的一級類對象
顯而易見,在python里函數和其他東西一樣都是對象。(此處應該大聲歌唱)啊!包含變量的函數,你也并不是那么特殊!
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你也許從沒有想過,你定義的函數居然會有屬性。沒辦法,函數在python里面就是對象(Python一切皆對象),和其他的東西一樣,也許這樣描述會太學院派太官方了點:在python里,函數只是一些普通的值而已和其他的值一毛一樣。這就是說你可以把函數像參數一樣傳遞給其他的函數或者說從函數里面返回函數!如果你從來沒有這么想過,那看看下面這個例子:
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這個例子對你來說應該不會很奇怪。add和sub是非常普通的兩個python函數,接受兩個值,返回一個計算后的結果值。在#1處你們能看到準備接收一個函數的變量只是一個普通的變量而已,和其他變量一樣。在#2處我們調用傳進來的函數:“()代表著調用的操作并且調用變量包含的值。在#3處,你們也能看到傳遞函數并沒有什么特殊的語法。” 函數的名稱只是很其他變量一樣的表標識符而已。
你們也許看到過這樣的行為:“python把頻繁要用的操作變成函數作為參數進行使用,像通過傳遞一個函數給內置排序函數的key參數從而來自定義排序規則。那把函數當做返回值回事這樣的情況呢:
def outer():def inner():print "Inside inner"return inner # 1 foo = outer() #2 foo #<function inner at 0x...> foo() #Inside inner這個例子看起來也許會更加的奇怪。在#1處我把恰好是函數標識符的變量inner作為返回值返回出來。這并沒有什么特殊的語法:”把函數inner返回出來,否則它根本不可能會被調用到。“還記得變量的生存周期嗎?每次函數outer被調用的時候,函數inner都會被重新定義,如果它不被當做變量返回的話,每次執行過后它將不復存在。
在#2處我們捕獲住返回值 – 函數inner,將它存在一個新變量foo里。我們能夠看到,當對變量foo進行求值,它確實包含函數inner,而且我們能夠對他進行調用。初次看起來可能會覺得有點奇怪,但是理解起來并不困難是吧。堅持住,因為奇怪的轉折馬上就要來了
?
8. 閉包
我們先不急著定義什么是閉包,先來看看一段代碼,僅僅是把上一個例子簡單的調整了一下:
def outer():x = 1def inner():print x # 1return inner foo = outer() foo.func_closure #(<cell at 0x...: int object at 0x...>,)在上一個例子中我們了解到,inner作為一個函數被outer返回,保存在一個變量foo,并且我們能夠對它進行調用foo()。不過它會正常的運行嗎?我們先來看看作用域規則。
所有的東西都在python的作用域規則下進行工作:“x是函數outer里的一個局部變量。當函數inner在#1處打印x的時候,python解釋器會在inner內部查找相應的變量,當然會找不到,所以接著會到封閉作用域里面查找,并且會找到匹配。
但是從變量的生存周期來看,該怎么理解呢?我們的變量x是函數outer的一個本地變量,這意味著只有當函數outer正在運行的時候才會存在。根據我們已知的python運行模式,我們沒法在函數outer返回之后繼續調用函數inner,在函數inner被調用的時候,變量x早已不復存在,可能會發生一個運行時錯誤。
萬萬沒想到,返回的函數inner居然能夠正常工作。Python支持一個叫做函數閉包的特性,用人話來講就是,嵌套定義在非全局作用域里面的函數能夠記住它在被定義的時候它所處的封閉命名空間。這能夠通過查看函數的func_closure屬性得出結論,這個屬性里面包含封閉作用域里面的值(只會包含被捕捉到的值,比如x,如果在outer里面還定義了其他的值,封閉作用域里面是不會有的)
記住,每次函數outer被調用的時候,函數inner都會被重新定義。現在變量x的值不會變化,所以每次返回的函數inner會是同樣的邏輯,假如我們稍微改動一下呢?
def outer(x):def inner():print x # 1return inner print1 = outer(1) print2 = outer(2) print1() 1 print2() 2從這個例子中你能夠看到閉包 – 被函數記住的封閉作用域 – 能夠被用來創建自定義的函數,本質上來說是一個硬編碼的參數。事實上我們并不是傳遞參數1或者2給函數inner,我們實際上是創建了能夠打印各種數字的各種自定義版本。
閉包單獨拿出來就是一個非常強大的功能, 在某些方面,你也許會把它當做一個類似于面向對象的技術:outer像是給inner服務的構造器,x像一個私有變量。使用閉包的方式也有很多:你如果熟悉python內置排序方法的參數key,你說不定已經寫過一個lambda方法在排序一個列表的列表的時候基于第二個元素而不是第一個。現在你說不定也可以寫一個itemgetter方法,接收一個索引值來返回一個完美的函數,傳遞給排序函數的參數key。
不過,我們現在不會用閉包做這么low的事(⊙o⊙)…!相反,讓我們再爽一次,寫一個高大上的裝飾器!
?
9. 裝飾器
裝飾器其實就是一個閉包,把一個函數當做參數然后返回一個替代版函數。我們一步步從簡到繁來瞅瞅:
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仔細看看上面這個裝飾器的例子。們定義了一個函數outer,它只有一個some_func的參數,在他里面我們定義了一個嵌套的函數inner。inner會打印一串字符串,然后調用some_func,在#1處得到它的返回值。在outer每次調用的時候some_func的值可能會不一樣,但是不管some_func的之如何,我們都會調用它。最后,inner返回some_func() + 1的值 – 我們通過調用在#2處存儲在變量decorated里面的函數能夠看到被打印出來的字符串以及返回值2,而不是期望中調用函數foo得到的返回值1。
我們可以認為變量decorated是函數foo的一個裝飾版本,一個加強版本。事實上如果打算寫一個有用的裝飾器的話,我們可能會想愿意用裝飾版本完全取代原先的函數foo,這樣我們總是會得到我們的”加強版“foo。想要達到這個效果,完全不需要學習新的語法,簡單地賦值給變量foo就行了:
foo = outer(foo) foo # doctest: +ELLIPSIS #<function inner at 0x...>現在,任何怎么調用都不會牽扯到原先的函數foo,都會得到新的裝飾版本的foo。
假設有如下函數:
def now():print '2013-12-25' f = now f() #2013-12-25現在假設我們要增強now()函數的功能,比如,在函數調用前后自動打印日志,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
本質上,decorator就是一個返回函數的高階函數。所以,我們要定義一個能打印日志的decorator,可以定義如下:
def log(func):def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper觀察上面的log,因為它是一個decorator,所以接受一個函數作為參數,并返回一個函數。
?
10. 使用 @ 標識符將裝飾器應用到函數
Python2.4支持使用標識符@將裝飾器應用在函數上,只需要在函數的定義前加上@和裝飾器的名稱。在上一節的例子里我們是將原本的方法用裝飾后的方法代替:
add = wrapper(add)這種方式能夠在任何時候對任意方法進行包裝。但是如果我們自定義一個方法,我們可以使用@進行裝飾:
@wrapper def add(a, b):return Coordinate(a.x + b.x, a.y + b.y)需要明白的是,這樣的做法和先前簡單的用包裝方法替代原有方法是一模一樣的, python只是加了一些語法糖讓裝飾的行為更加的直接明確和優雅一點。
多個decorator
@decorator_one @decorator_two def func():pass相當于:
func = decorator_one(decorator_two(func))比如:帶參數的decorator:
@decorator(arg1, arg2) def func():pass相當于:
func = decorator(arg1,arg2)(func)這意味著decorator(arg1, arg2)這個函數需要返回一個“真正的decorator”。
?
11. *args and **kwargs
我們已經完成了一個有用的裝飾器,但是由于硬編碼的原因它只能應用在一類具體的方法上,這類方法接收兩個參數,傳遞給閉包捕獲的函數。如果我們想實現一個能夠應用在任何方法上的裝飾器要怎么做呢?再比如,如果我們要實現一個能應用在任何方法上的類似于計數器的裝飾器,不需要改變原有方法的任何邏輯。這意味著裝飾器能夠接受擁有任何簽名的函數作為自己的被裝飾方法,同時能夠用傳遞給它的參數對被裝飾的方法進行調用。
非常巧合的是Python正好有支持這個特性的語法。可以閱讀 Python Tutorial 獲取更多的細節。當定義函數的時候使用了*,意味著那些通過位置傳遞的參數將會被放在帶有*前綴的變量中, 所以:
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',)第一個函數one只是簡單地講任何傳遞過來的位置參數全部打印出來而已,你們能夠看到,在代碼#1處我們只是引用了函數內的變量args, *args僅僅只是用在函數定義的時候用來表示位置參數應該存儲在變量args里面。Python允許我們制定一些參數并且通過args捕獲其他所有剩余的未被捕捉的位置參數,就像#2處所示的那樣。
*操作符在函數被調用的時候也能使用。意義基本是一樣的。當調用一個函數的時候,一個用*標志的變量意思是變量里面的內容需要被提取出來然后當做位置參數被使用。同樣的,來看個例子:
#1處的代碼和#2處的代碼所做的事情其實是一樣的,在#2處,python為我們所做的事其實也可以手動完成。這也不是什么壞事,*args要么是表示調用方法大的時候額外的參數可以從一個可迭代列表中取得,要么就是定義方法的時候標志這個方法能夠接受任意的位置參數。
接下來提到的**會稍多更復雜一點,**代表著鍵值對的餐宿字典,和*所代表的意義相差無幾,也很簡單對不對:
當我們定義一個函數的時候,我們能夠用**kwargs來表明,所有未被捕獲的關鍵字參數都應該存儲在kwargs的字典中。如前所訴,args、kwargs并不是python語法的一部分,但在定義函數的時候,使用這樣的變量名算是一個不成文的約定。和*一樣,我們同樣可以在定義或者調用函數的時候使用**。
dct = {'x': 1, 'y': 2} def bar(x, y):return x + y bar(**dct) #3?
12. 更通用的裝飾器
有了這招新的技能,我們隨隨便便就可以寫一個能夠記錄下傳遞給函數參數的裝飾器了。先來個簡單地把日志輸出到界面的例子:
def logger(func):def inner(*args, **kwargs): #1print "Arguments were: %s, %s" % (args, kwargs)return func(*args, **kwargs) #2return inner請注意函數inner,它能夠接受任意數量和類型的參數并把它們傳遞給被包裝的方法,這讓我們能夠用這個裝飾器來裝飾任何方法。
@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隨便調用我們定義的哪個方法,相應的日志也會打印到輸出窗口,和我們預期的一樣。
?
13. 帶參數的裝飾器:
如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,寫出來會更復雜。比如,要自定義log的文本:
def log(text):def decorator(func):def wrapper(*args, **kw):print '%s %s():' % (text, func.__name__)return func(*args, **kw)return wrapperreturn decorator這個3層嵌套的decorator用法如下:
@log('execute') def now():print '2013-12-25'執行結果如下:
>>> now() execute now(): 2013-12-25和兩層嵌套的decorator相比,3層嵌套的效果是這樣的:
now = log('execute')(now)我們來剖析上面的語句,首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數。
?
14. 裝飾器的副作用
以上兩種decorator的定義都沒有問題,但還差最后一步。因為我們講了函數也是對象,它有__name__等屬性,但你去看經過decorator裝飾之后的函數,它們的__name__已經從原來的'now'變成了'wrapper':
>>> now.__name__ 'wrapper'因為返回的那個wrapper()函數名字就是'wrapper',所以,需要把原始函數的__name__等屬性復制到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。
不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內置的functools.wraps就是干這個事的,所以,一個完整的decorator的寫法如下:
import functoolsdef log(func):@functools.wraps(func)def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper或者針對帶參數的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是導入functools模塊。模塊的概念稍候講解。現在,只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。
當然,即使是你用了functools的wraps,也不能完全消除這樣的副作用。你會發現,即使是你你用了functools的wraps,你在用getargspec時,參數也不見了。要修正這一問題,我們還得用Python的反射來解決,當然,我相信大多數人的程序都不會去getargspec。所以,用functools的wraps應該夠用了。
?
15. class式的 Decorator
首先,先得說一下,decorator的class方式,還是看個示例:
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)一個是__init__(),這個方法是在我們給某個函數decorator時被調用,所以,需要有一個fn的參數,也就是被decorator的函數。
2)一個是__call__(),這個方法是在我們調用被decorator函數時被調用的。
上面輸出可以看到整個程序的執行順序。
這看上去要比“函數式”的方式更易讀一些。
上面這段代碼中,我們需要注意這幾點:
1)如果decorator有參數的話,__init__() 成員就不能傳入fn了,而fn是在__call__的時候傳入的。
?
?
16. 一些decorator的示例
好了,現在我們來看一下各種decorator的例子:
?
16.1 給函數調用做緩存
這個例實在是太經典了,整個網上都用這個例子做decorator的經典范例,因為太經典了,所以,我這篇文章也不能免俗。
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)上面這個例子中,是一個斐波拉契數例的遞歸算法。我們知道,這個遞歸是相當沒有效率的,因為會重復調用。比如:我們要計算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上來說,fib(3), fib(2), fib(1)在整個遞歸過程中被調用了兩次。
而我們用decorator,在調用函數前查詢一下緩存,如果沒有才調用了,有了就從緩存中返回值。一下子,這個遞歸從二叉樹式的遞歸成了線性的遞歸。
?
16.2 Profiler的例子
這個例子沒什么高深的,就是實用一些。
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 注冊回調函數
下面這個示例展示了通過URL的路由來調用相關注冊的函數示例:
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)上面這個示例中,用類的實例來做decorator。
2)decorator類中沒有__call__(),但是wrapper返回了原函數。所以,原函數沒有發生任何變化。
?
16.4 給函數打日志
下面這個示例演示了一個logger的decorator,這個decorator輸出了函數名,參數,返回值,和運行時間。
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)上面那個打日志還是有點粗糙,讓我們看一個更好一點的(帶log level參數的):
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但是,上面這個帶log level參數的有兩具不好的地方,
1) loglevel不是debug的時候,還是要計算函數調用的時間。
2) 不同level的要寫在一起,不易讀。
我們再接著改進:
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你可以看到兩點,
1)我們分了兩個log level,一個是info的,一個是debug的,然后我們在外尾根據不同的參數返回不同的decorator。
2)我們把info和debug中的相同的代碼抽到了一個叫_basic_log的函數里,DRY原則。
?
16.5 一個MySQL的Decorator
下面這個decorator是我在工作中用到的代碼,我簡化了一下,把DB連接池的代碼去掉了,這樣能簡單點,方便閱讀。
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 線程異步
下面量個非常簡單的異步執行的decorator,注意,異步處理并不簡單,下面只是一個示例。
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?超時函數
這個函數的作用在于可以給任意可能會hang住的函數添加超時功能,這個功能在編寫外部API調用 、網絡爬蟲、數據庫查詢的時候特別有用。
timeout裝飾器的代碼如下:
# coding=utf-8 # 測試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函數如果在5s內不返回就強制拋TimeoutError Exception結束 def slowfunc(sleep_time):a = 1import timetime.sleep(sleep_time)return a# slowfunc(3) #sleep 3秒,正常返回 沒有異常print slowfunc(11) # 被終止?
16.8 Trace函數
有時候出于演示目的或者調試目的,我們需要程序運行的時候打印出每一步的運行順序 和調用邏輯。類似寫bash的時候的bash -x調試功能,然后Python解釋器并沒有 內置這個時分有用的功能,那么我們就“自己動手,豐衣足食”。
Trace裝飾器的代碼如下:
# coding=utf-8 # 測試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() #調用####################################### 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 # 測試utf-8編碼 # 單例裝飾器 import sys reload(sys) sys.setdefaultencoding('utf-8')# 使用裝飾器實現簡單的單例模式 def singleton(cls):instances = dict() # 初始為空def _singleton(*args, **kwargs):if cls not in instances: #如果不存在, 則創建并放入字典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
下面要分享的這個LRUCache不是我做的,是github上的一個庫,我們在實際環境中有用到。
先來說下這個概念,cache的意思就是緩存,LRU就是Least Recently Used,即最近最少使用,是一種內存管理算法。總結來說這就是一種緩存方法,基于時間和容量。
一般在簡單的python程序中,遇到需要處理緩存的情況時最簡單的方式,聲明一個全局的dict就能解決(在python中應盡量避免使用全局變量)。但是只是簡單情況,這種情況會帶來的問題就是內存泄漏,因為可能會出現一直不命中的情況。
由此導致的一個需求就是,你要設定這個dict的最大容量,防止發生泄漏。但僅僅是設定最大容量是不夠的,設想當你的dict變量已被占滿,還是沒有命中,該如何處理。
這時就需要加一個失效時間了。如果在指定失效期內沒有使用到該緩存,則刪除。
綜述上面的需求和功能就是LRUCache干的事了。不過這份代碼做了更進一層的封裝,可以讓你直接把緩存功能做為一個裝飾器來用。具體實現可以去參考代碼,別人實現之后看起來并不復雜 :)
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開始內置在functools了lru_cache的功能,說明這個需求是很普遍的。
?
?
17. 小結
在面向對象(OOP)的設計模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實現,而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數實現,也可以用類實現。
decorator可以增強函數的功能,定義起來雖然有點復雜,但使用起來非常靈活和方便。
最后留個小作業:
請編寫一個decorator,能在函數調用的前后打印出'begin call'和'end call'的日志。
再思考一下能否寫出一個@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修飾器的函數式編程
http://coolshell.cn/articles/11265.html
[4]?Python Decorator Library
https://wiki.python.org/moin/PythonDecoratorLibrary
[5]?Python裝飾器實例:調用參數合法性驗證
http://python.jobbole.com/82114/
[6]?Python 裝飾器
http://python.jobbole.com/82344/
[7]?兩個實用的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 中的閉包總結
http://dwz.cn/2CiO78
[9]?Python 的閉包和裝飾器
https://segmentfault.com/a/1190000004461404
[10]?Python修飾器的問題
https://segmentfault.com/q/1010000004595899
?
?
?
?
總結
以上是生活随笔為你收集整理的Python 装饰器 函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++常用字符串分割方法
- 下一篇: Python集合(set)类型的操作