一周一個(gè)Python語(yǔ)法糖:(一)裝飾器

Decorator

首先,我們來(lái)認(rèn)識(shí)一下裝飾器是什么:
裝飾器是給現(xiàn)有的模塊增添新的小功能
(在不改變?cè)心K功能的基礎(chǔ)上)

假如我有個(gè)簡(jiǎn)單筆,它只能用一種顏色進(jìn)行寫(xiě)字
我現(xiàn)在給它加上一只筆芯,它能換種顏色寫(xiě)字(又能換回來(lái)~)
這就是裝飾器的樸素比喻

一、初探裝飾器

手動(dòng)寫(xiě)個(gè)裝飾器吧


#自定義裝飾函數(shù)
def decorator(fn):
    def wrapper(*args):
        #這里裝飾器的作用是在函數(shù)調(diào)用前增加一句話表示裝飾成功

        print("this is decorator fo %s" % fn.__name__)
        fn(*args)
    return wrapper
def hello(name):
    print('hello,%s' %name)
if __name__=="__main__":
    #用賦值的形式進(jìn)行裝飾器
    hello=decorator(hello)
    hello("cool")

輸出結(jié)果為:

Paste_Image.png

首先,我們要知道,在python中,函數(shù)也是一種對(duì)象(萬(wàn)物皆對(duì)象?。?/p>

  1. 函數(shù)可以賦值給一個(gè)變量(學(xué)過(guò)C語(yǔ)言的可以聯(lián)想下函數(shù)指針)

  2. 函數(shù)可以定義在另一個(gè)函數(shù)內(nèi)部

這也意味著一個(gè)函數(shù)可以返回另一個(gè)函數(shù)

hello=decorator(hello)

這一句代碼中,將hello函數(shù)作為變量傳入decorator裝飾器中,然后hello方法在decorator中的函數(shù)wrapper函數(shù)實(shí)現(xiàn),同時(shí)包裝新的功能,將新的函數(shù)wrapper作為變量返回 ,所以hello的新值是 經(jīng)過(guò)decorator裝飾的wrapper新方法。

所以,裝飾器裝飾函數(shù)的時(shí)候,將函數(shù)作為變量傳入裝飾器內(nèi)部,實(shí)際調(diào)用的是裝飾器內(nèi)部的函數(shù)(添加新功能之后的函數(shù))

二、 @語(yǔ)法糖

Python 中裝飾器語(yǔ)法并不用每次都用賦值語(yǔ)句。
在函數(shù)定義的時(shí)候就加上@+裝飾器名字即可
再來(lái)我們剛才的例子吧:

def decorator(fn):
    def wrapper(*args):
        print("this is decorator fo %s" % fn.__name__)
        fn(*args)
    return wrapper
@decorator
def hello(name):
    print('hello,%s' %name)
if __name__=="__main__":
    hello("cool")

2.裝飾器的順序:

比如我們有兩個(gè)裝飾器:

@decorator_one
@decorator_two
def hello()
    pass

這句代碼實(shí)際上類似于:

hello=decorator_one(decorator_two(hello))

兩個(gè)裝飾器一層層地往外裝飾

3.帶參數(shù)的裝飾器

我們說(shuō)過(guò),裝飾器其實(shí)也是一種函數(shù),所以它自身也是能帶參數(shù)的

@decorator(arg1, arg2)
def func():
    pass

類似于

func = decorator(arg1,arg2)(func)

來(lái)個(gè)實(shí)際點(diǎn)的例子吧:
我們手寫(xiě)html的時(shí)候需要各種補(bǔ)全(那個(gè)用編輯器的當(dāng)然爽得飛起?。?br> 但是,如果是在python中用字符串去表示html標(biāo)簽的時(shí)候,就~坑爹了
。總不能每個(gè)標(biāo)簽我都寫(xiě)一個(gè)方法吧
最方便的方法,寫(xiě)一個(gè)帶參數(shù)的裝飾器!
HTML.py

def makeHtmlTag(tag, *args, **kwargs):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwargs["css_class"]) \
                                     if "css_class" in kwargs else ""
        def wrapped(*args, **kwargs):
            return "<"+tag+css_class+">" + fn(*args, **kwargs) + "</"+tag+">"
        return wrapped
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print(hello())
 
print(hello())
 

運(yùn)行結(jié)果:

Paste_Image.png

關(guān)于幾點(diǎn)說(shuō)明:
(1)在裝飾器makeHtmlTag中,*args代表了參數(shù)元組,假如你傳入的的參數(shù)分別是1,2,3,4,則args=(1,2,3,4)
**kwds則參數(shù)字典,返回一個(gè)key為參數(shù)變量名,value為變量值的字典
這樣我們就可以很方便地不用改動(dòng)函數(shù)本身去獲取函數(shù)傳遞的參數(shù)并進(jìn)行裝飾了
(2)使用裝飾器的時(shí)候有個(gè)缺陷就是不能在過(guò)程中更改某個(gè)裝飾器的參數(shù)值(比如該例子中 hello 的便簽就永遠(yuǎn)是b,i了)

如果你覺(jué)得這樣寫(xiě)太!麻!煩!了!什!么!鬼!
為什么我要在函數(shù)體中再定義一個(gè)函數(shù)體?。。?!
難道還要我一層層剝開(kāi)你的心嗎?

4. 用類的方式去寫(xiě)一個(gè)裝飾器

class makeHtmlTagClass(object):
 
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class !="" else ""
 
    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped
 
@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)
 
print hello("Hao Chen")

關(guān)于說(shuō)明:
(1)我們將整個(gè)類作為一個(gè)裝飾器,工作流程:
通過(guò)__init__()方法初始化類
通過(guò)__call__()方法調(diào)用真正的裝飾方法
(2)當(dāng)裝飾器有參數(shù)的時(shí)候,init() 成員就不能傳入fn了,而fn是在call的時(shí)候傳入的。(fn代表要裝飾的函數(shù))

decorator萬(wàn)能的?

No!No!No!
有時(shí)候我想加入日志系統(tǒng)。來(lái)記錄我某個(gè)函數(shù)的運(yùn)行情況。

import time
def logger(fn):
    def wrapper(*args,**kwargs):
        ts=time.time()
        print('start run the function %s' % fn.__name__)
        result=fn(*args,**kwargs)
        te=time.time()
        print('run end! it continue %.2f '%(te-ts))
        return result
    return wrapper

@logger
def hello():
    print('start running')
    time.sleep(2)
    return 2

hello()
print(hello.__name__)

運(yùn)行結(jié)果:

Paste_Image.png

W T F?

我的hello.__name__不是應(yīng)該是hello嗎?
唯一解釋:就想一開(kāi)始說(shuō)的,裝飾器原理:

hello=decorator(hello)

hello實(shí)際上已經(jīng)變成了經(jīng)過(guò)裝飾器修飾的方法了
(主公,我身在曹營(yíng)心在漢呀!?。。。?/strong>
怎么辦?
Python的functool包中提供了一個(gè)叫wrap的decorator來(lái)消除這樣的副作用
新版logger.py


from functools import wraps
import time
def logger(fn):
    @wraps(fn)
    def wrapper(*args,**kwargs):
        ts=time.time()
        print('start run the function %s' % fn.__name__)
        result=fn(*args,**kwargs)
        te=time.time()
        print('run end! it continue %.2f '%(te-ts))
        return result
    return wrapper

@logger
def hello():
    print('start running')
    time.sleep(2)
    return 2

hello()
print(hello.__name__)

運(yùn)行結(jié)果:

Paste_Image.png

5.裝飾器獲取參數(shù)的值
比如我某個(gè)函數(shù)是批量運(yùn)行的,我需要加個(gè)日志系統(tǒng)來(lái)知道這個(gè)函數(shù)進(jìn)行了多少次,,這就需要獲取函數(shù)運(yùn)行時(shí)的參數(shù)了
方法:用inspect模塊的getcallargs方法去獲取原函數(shù)的參數(shù)
返回的是一個(gè)字典,根據(jù)字典的key去獲取參數(shù)的值

from functools import wraps
from inspect import getcallargs
class Logger(object):
    def __init__(self,filename=''):
        if filename!='':
            self._filename=filename
        else:
            self._filename="log/errorlog"
    def __call__(self,fn):
        @wraps(fn)
        def wrapper(*args,**kwargs):
            func_args=getcallargs(fn,*args,**kwargs)
            if 'param1' in func_args.keys():
                param1=func_args['param1']
            if 'param2' in func_args.keys():
                param2=func_args['param2']
            with open(self._filename,'a') as logfile_handle:
                logfile_handle.write(param1+'/'+param2+'\t finished\n')
                logfile_handle.flush()
            result=fn(*args,**kwargs)
            return result
        return wrapper

@Logger(filename='testlog')
def test(param1,param2,param3,param4,param5):
    print('Ok,finished')
    return param5

if __name__=="__main__":
    print(test('1','2','3','4','5'))
    print(test('s','g','r','e','b'))

運(yùn)行結(jié)果:

2017-04-03 19-29-07屏幕截圖.png

打開(kāi)日志文件:

2017-04-03 19-29-56屏幕截圖.png

我們拿到了函數(shù)的第一個(gè)參數(shù)跟第二個(gè)參數(shù)的值并保存到文件中

一些裝飾器的例子(日志那個(gè)請(qǐ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] = result
        return result
 
    return wrapper
 
@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

這是斐波拉契數(shù)例的遞歸算法。
因?yàn)閒ib(1) fib(0)是重復(fù)計(jì)算的值,將其利用裝飾器預(yù)先緩存(先計(jì)算好fib(1),fib(0)),調(diào)用的時(shí)候直接返回字典的值,達(dá)到優(yōu)化fib(n)的思路
很經(jīng)典
當(dāng)我讀懂這段代碼的時(shí)候,
我內(nèi)心大喊一聲:WO CAO! 還能這樣玩!

2.web后端通過(guò)URL的路由來(lái)調(diào)用相關(guān)注冊(cè)

class MyApp():
    def __init__(self):
        self.func_map = {}
 
    def register(self, name):
        def func_wrapper(func):
            self.func_map[name] = func
            return func
        return func_wrapper
 
    def 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')

注:decorator類中沒(méi)有call(),但是wrapper返回了原函數(shù)。所以,原函數(shù)沒(méi)有發(fā)生任何變化。
這例子只是用來(lái)注冊(cè)u(píng)rl 方法并調(diào)用
防止web訪問(wèn)位置url~

無(wú)返回值的異步多線程調(diào)用(涉及數(shù)據(jù)變化請(qǐng)用鎖)

from threading import Thread
from functools import wraps
 
def async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl
 
    return async_func
 
if __name__ == '__main__':
    from time import sleep
 
    @async
    def 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()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容