一周一個Python語法糖:(一)裝飾器

Decorator

首先,我們來認識一下裝飾器是什么:
裝飾器是給現有的模塊增添新的小功能
(在不改變原有模塊功能的基礎上)

假如我有個簡單筆,它只能用一種顏色進行寫字
我現在給它加上一只筆芯,它能換種顏色寫字(又能換回來~)
這就是裝飾器的樸素比喻

一、初探裝飾器

手動寫個裝飾器吧


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

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

輸出結果為:

Paste_Image.png

首先,我們要知道,在python中,函數也是一種對象(萬物皆對象?。?/p>

  1. 函數可以賦值給一個變量(學過C語言的可以聯想下函數指針)

  2. 函數可以定義在另一個函數內部

這也意味著一個函數可以返回另一個函數

hello=decorator(hello)

這一句代碼中,將hello函數作為變量傳入decorator裝飾器中,然后hello方法在decorator中的函數wrapper函數實現,同時包裝新的功能,將新的函數wrapper作為變量返回 ,所以hello的新值是 經過decorator裝飾的wrapper新方法。

所以,裝飾器裝飾函數的時候,將函數作為變量傳入裝飾器內部,實際調用的是裝飾器內部的函數(添加新功能之后的函數)

二、 @語法糖

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

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.裝飾器的順序:

比如我們有兩個裝飾器:

@decorator_one
@decorator_two
def hello()
    pass

這句代碼實際上類似于:

hello=decorator_one(decorator_two(hello))

兩個裝飾器一層層地往外裝飾

3.帶參數的裝飾器

我們說過,裝飾器其實也是一種函數,所以它自身也是能帶參數的

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

類似于

func = decorator(arg1,arg2)(func)

來個實際點的例子吧:
我們手寫html的時候需要各種補全(那個用編輯器的當然爽得飛起?。?br> 但是,如果是在python中用字符串去表示html標簽的時候,就~坑爹了
。總不能每個標簽我都寫一個方法吧
最方便的方法,寫一個帶參數的裝飾器!
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())
 

運行結果:

Paste_Image.png

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

如果你覺得這樣寫太!麻!煩!了!什!么!鬼!
為什么我要在函數體中再定義一個函數體!?。?!
難道還要我一層層剝開你的心嗎?

4. 用類的方式去寫一個裝飾器

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")

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

decorator萬能的?

No!No!No!
有時候我想加入日志系統(tǒng)。來記錄我某個函數的運行情況。

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__)

運行結果:

Paste_Image.png

W T F?

我的hello.__name__不是應該是hello嗎?
唯一解釋:就想一開始說的,裝飾器原理:

hello=decorator(hello)

hello實際上已經變成了經過裝飾器修飾的方法了
(主公,我身在曹營心在漢呀?。。。。?/strong>
怎么辦?
Python的functool包中提供了一個叫wrap的decorator來消除這樣的副作用
新版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__)

運行結果:

Paste_Image.png

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

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'))

運行結果:

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

打開日志文件:

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

我們拿到了函數的第一個參數跟第二個參數的值并保存到文件中

一些裝飾器的例子(日志那個請看上文)

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)

這是斐波拉契數例的遞歸算法。
因為fib(1) fib(0)是重復計算的值,將其利用裝飾器預先緩存(先計算好fib(1),fib(0)),調用的時候直接返回字典的值,達到優(yōu)化fib(n)的思路
很經典
當我讀懂這段代碼的時候,
我內心大喊一聲:WO CAO! 還能這樣玩!

2.web后端通過URL的路由來調用相關注冊

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類中沒有call(),但是wrapper返回了原函數。所以,原函數沒有發(fā)生任何變化。
這例子只是用來注冊url 方法并調用
防止web訪問位置url~

無返回值的異步多線程調用(涉及數據變化請用鎖)

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()
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容