Python decorator裝飾器

文中知識點和代碼示例學習自慕課網,python進階部分(http://www.imooc.com/learn/317) .學習筆記

裝飾器的理解

裝飾器本質上是一個高階函數,接受一個函數,進行處理,然后返回一個新的函數。

# 有一個簡單的函數
def f1(x):
    return x * 2
print f1(5)

#輸出:10

# 實現運行該函數時,輸出該函數的名稱的日志功能
# 法一:直接在函數中寫出,好理解,但是如果函數很多,那每個函數都要加一遍
def f1(x):
    print "call " + f1.__name__ + "().."
    return x*2
print f1(5)

#輸出:
#call f1()..
#10

# 法二:使用裝飾器,創建裝飾器函數
def flog(f):        #定義裝飾器函數,接受參數是 f 函數
    def fn(x):      #定義新的函數,來處理 f 函數,添加我們需要的日志信息,并返回 f 函數,參數 x 是 f1 的參數,如果 f1 函數有多個,這邊也要寫多個,要讓 @log 自適應任何參數定義的函數,可以利用Python的 *args 和 **kw,保證任意個數的參數總是能正常調用
        print "call " + f.__name__ + "().." 
        return f(x) #執行原 f 函數
    return fn       #返回新的函數

# 調用裝飾器,效果和 g1 = flog(f1);print g1(5) 一致。
@flog               
def f1(x):
    return x * 2
print f1(5)

#輸出:
#call f1()..
#10

注: 上邊f1(x)只接受一個函數,如果接受兩個函數就會報錯,要讓 @flog 自適應任何參數定義的函數,可以利用Python的 *args**kw,保證任意個數的參數總是能正常調用

例:計算函數調用的時間可以記錄調用前后的當前時間戳,然后計算兩個時間戳的差。

import time

# 定義裝飾器
def performance(f):
    def fn(*args, **kw):    #可以接受任意參數
        t1 = time.time()    #記錄執行前的時間
        r = f(*args, **kw)  #執行原函數,并保存執行結果到變量 r 中
        t2 = time.time()    #記錄執行結束后的時間
        print 'call %s() in %fs' % (f.__name__, (t2 - t1))  #添加自定義輸出內容
        return r            #返回原函數的執行結果
    return fn               #返回新函數

#調用裝飾器
@performance
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

帶參數的裝飾器

上邊的實現的裝飾器函數,只能輸出固定的內容,除了f.__name__所定義的函數名稱。如果想要根據函數的不同來給輸出的日志劃分等級的,如 a 函數日志等級為info,b 函數日志等級為debug,這里需要用到帶參數的裝飾器。如:

@log('DEBUG')
def my_func():
    pass

@log('DEBUG')等于之前無參數的裝飾器的@log,即@log('DEBUG')這個返回的函數相當于之前無參數裝飾器的@log。所以要在無參數的裝飾器上層再加一個函數的嵌套。

例:輸出日志等級

#coding=utf-8

"""
    帶參數的裝飾器,寫三層嵌套的函數
"""

def flog(devel):    # 定義帶參數的裝飾器.
    def log_decorator(f):   #和無參數的裝飾器相同
        def add_self(*args,**kwargs):   #添加輸出的日志,執行f函數
            print "[%s],call %s().." % (devel,f.__name__)
            return  f(*args,**kwargs)
        return add_self     
    return log_decorator    #返回給flog("INFO")

@flog("INFO")
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

"""
輸出
[INFO],call factorial()..
3628800
"""

例2:上一節的@performance只能打印秒,請給@performace增加一個參數,允許傳入's''ms'

import time
def performance(unit):s
    def perf_decorator(f):
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
            print 'call %s() in %f %s' % (f.__name__, t, unit)
            return r
        return wrapper
    return perf_decorator

@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

完善裝飾器

經過@decorator“改造”后的函數,和原函數相比會有不同的地方,如:

# 在沒有decorator的情況下,打印函數名:
def f1(x):
    pass
print f1.__name__

#輸出:f1

# 有decorator的情況下,再打印函數名:
def log(f):
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper
@log
def f2(x):
    pass
print f2.__name__

#輸出:wrapper

可見,由于decorator返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。這對于那些依賴函數名的代碼就會失效。decorator還改變了函數的__doc__等其它屬性。如果要讓調用者看不出一個函數經過了@decorator的“改造”,就需要把原函數的一些屬性復制到新函數中。

Python內置的functools可以用來自動化完成這個“復制”的任務:

import functools
def log(f):
    @functools.wraps(f)     #增加該行
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper

最后需要指出,由于我們把原函數簽名改成了(*args, **kw),因此,無法獲得原函數的原始參數信息。即便我們采用固定參數來裝飾只有一個參數的函數

例:該方法使用到上節的例子中

import time, functools
def performance(unit):
    def perf_decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            t = (t2 - t1) * 1000 if unit=='ms' else (t2 - t1)
            print 'call %s() in %f %s' % (f.__name__, t, unit)
            return r
        return wrapper
    return perf_decorator

@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial.__name__
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 前言 Python的修飾器的英文名叫Decorator,當你看到這個英文名的時候,你可能會把其跟Design Pa...
    linheimx閱讀 637評論 0 4
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,951評論 19 139
  • 要點: 函數式編程:注意不是“函數編程”,多了一個“式” 模塊:如何使用模塊 面向對象編程:面向對象的概念、屬性、...
    victorsungo閱讀 1,563評論 0 6
  • 裝飾器 裝飾器本質上是一個Python函數,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返...
    時間之友閱讀 2,307評論 0 3
  • 美國作家杜魯門·卡波蒂在接受采訪時曾說: 我總是會有幻覺,認為故事的開端、中間和結尾,整出戲都在我頭腦里同時上演—...
    兔U閱讀 1,977評論 5 17