初窺Python(五)——python中的decorator



1. 介紹

decorator是用來在代碼運行期間動態增加功能的,本質上是一個返回函數的高階函數。假設現在有這樣一種需求,即在每個函數調用前記錄日誌,記錄被調用的函數名稱,可以這樣實現:

def log(func):
    def wrapper(*args, **kwargs):
        print "CALL %s()" % func.__name__
        return func(*args, **kwargs)
    return wrapper

def sayHi():
    print "Hi, Buddy."

def sayHello():
    print "Hello, Buddy."

# 調用函數,記錄日誌
log(sayHi)()
# 輸出爲
# CALL sayHi()
# Hi, Buddy.
log(sayHello)()
# 輸出爲
# CALL sayHello()
# Hello, Buddy.

這種方法確實實現了記錄日誌的功能,但每次這麼調用未免太過繁瑣,decorator因此出現。

2. 使用

其實,之前定義的log函數即爲一個decorator,只是使用方式不正確:

@log
def sayHi():
    print "Hi, Buddy."

sayHi()
# 輸出爲
# CALL sayHi()
# Hi, Buddy.

@log
def sayHello():
    print "Hello, Buddy."

sayHello()
# 輸出爲
# CALL sayHello()
# Hello, Buddy.

可以看到,使用decorator非常簡單方便,def sayHi():前的@log相當於將sayHi作爲參數傳入log函數中,並將返回值賦給sayHi,即:

sayHi = log(sayHi)

但是細心的讀者不難發現,這樣一來函數sayHi__name__屬性發生變化,由之前的sayHi變爲wrapper,使用python內置的functools.wraps方法可以解決這一問題,改造後的decorator如下:

import functools 

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print "CALL %s()" % func.__name__
        return func(*args, **kwargs)
    return wrapper

讓我們更進一步,使用三層嵌套的decorator,允許再多傳入一次參數:

import functools

def log(text="CALL")
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print "%s %s()" % (text, func.__name__)
            return func(*args, **kwargs)
        return wrapper
    return decorator

這次我們可以選擇傳入一個表示函數運行狀態的字符串,由於多了一層嵌套,使用時也會有些變化:

@log("EXECUTE")
def sayHi():
    print "Hi, Buddy."

sayHi()
# 輸出爲
# EXECUTE sayHi()
# Hi, Buddy.

修改過後嵌套使用爲:

sayHi = log("EXECUTE")(sayHi)

3. 拓展

3.1

修改log函數,使該decorator既可以通過

@log

使用,又可以通過

@log("EXECUTE")

使用:

import functools

def log(text="CALL"):
    if callable(text):
        func = text
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print "CALL %s()" % func.__name__
            return func(*args, **kwargs)
    else:
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                print "%s %s()" % (text, func.__name__)
                return func(*args, **kwargs)
            return wrapper
        return decorator

兩種使用方式:

@log
def sayHi():
    print "Hi, Buddy."

sayHi()
# 輸出爲
# CALL sayHi()
# Hi, Buddy.

@log("EXECUTE")
def sayHello():
    print "Hello, Buddy."

sayHello()
# 輸出爲
# EXECUTE sayHello()
# Hello, Buddy.
3.2

修改log函數,使該decorator在函數調用前及函數調用後分別輸出一條日誌:

import functools

def log(func):
    def wrapper(*args, **kwargs):
        print "CALL BEGINNING"
        call = func(*args, **kwargs)
        print "CALL ENDING"
        return call
    return wrapper

使用該decorator

@log
def sayHi():
    print "Hi, Buddy."

sayHi()
# 輸出爲
# CALL BEGINING
# Hi, Buddy.
# CALL ENDING

參考資料:

廖雪峯的官方網站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章