1.不帶參數的function decorator

def add_log(func):
    def newFunc(*args, **kwargs):
        logger.debug("Before %s() call" % func.__name__)
        res = func(*args, **kwargs)
        logger.debug("After %s() call" % func.__name__)
        return res
    return newFunc

# 使用前面提到的例子,但稍作改動
# 在函數上面加個@修飾符號,就等於前面提到的funcA = add_log(funcA)

@add_log
def funcA():
    print "In funcA"

@add_log
def funcC(x, y):
    print x + y

funcA()
funcC(2, y=3)
# decorator可以使用多個,呼叫的順序由下而上,也就是離定義函數最近的先被呼叫

@second
@first
def func():
    print('test')
# 等於 func = second(first(func))
func()

2.帶參數的function decorator

# 參數可以帶多個,而且各種型別皆可,就跟定義函數一樣
import time
# 傳入一個timeFormat讓我們可以決定輸出的時間格式
def add_log(logger, timeFormat="%b %d %Y - %H:%M:%S"):
    def decorator(func):
        def newFunc(*args, **kwargs):
            logger.debug("Start {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            res = func(*args, **kwargs)
            logger.debug("Finish {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            return res
        return newFunc
    return decorator

@add_log(logger)
def funcA():
    print("In funcA")

@add_log(logger, timeFormat="%m-%d-%Y %H:%M:%S")
def funcB():
    print("In funcA")

# 等同於 funcA = add_log(logger)(funcA)

funcA()
funcB()

3.保留__name__、__doc__

# 如果用上面例子print(funcA.__name__)會發現結果是newFunc!
# 原因是當使用decorator修飾函數時,__name__和__doc__都會被修改成閉包裡的函數,這樣當要查看某函數的doc string或要debug時會被搞混
# 解法如下
from functools import wraps
def add_log(logger, timeFormat="%b %d %Y - %H:%M:%S"):
    def decorator(func):
        @wraps(func)    # 透過functools的wraps函數,參數是傳入的func
        def newFunc(*args, **kwargs):
            logger.debug("Start {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            res = func(*args, **kwargs)
            logger.debug("Finish {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            return res
        return newFunc
    return decorator

@add_log(logger)
def funcA():
    print("In funcA")
print(funcA.__name__) # 此時結果才會是funcA

4.class decorator

# 概念上是一樣的,但變得比較複雜
import logging
from functools import wraps
import time
LOG_FILE = "test.log"
logging.basicConfig(filename = LOG_FILE, level = logging.DEBUG)

def add_log(logger, timeFormat = "%b %d %Y - %H:%M:%S"):
    def decorator(func):
        # 跟前面範例一樣,這裡主要是檢查是否該函數已經被logger decorator修飾過了
        if hasattr(func, "_logger_added") and func._logger_added:
            return func

        @wraps(func)
        def newFunc(*args, **kwargs):
            logger.debug("Start {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            res = func(*args, **kwargs)
            logger.debug("Finish {0} call on {1}".format(func.__name__, time.strftime(timeFormat)))
            return res
        # 設置這個變數,代表該function已經被此decorator修飾過
        newFunc._logger_added = True    
        return newFunc
    return decorator

def log_methods(timeFormat = "%b %d %Y - %H:%M:%S"):
    def decorator(clz):
        logger = logging.getLogger(clz.__name__)
        # Iterate the name of member (methods, attributes) of the class
        for member_name in dir(clz):
            if member_name.startswith("__"):   # built-in函數,不做log處理
                continue
            member = getattr(clz, member_name)      # Get the member
            if hasattr(member, "__call__"):    # 確認對象是可呼叫的函數
                decorated_func = add_log(logger, timeFormat)(member) # 用上面定義好的decorator
                # 把被修飾過的函數,設置在class屬性裡
                setattr(clz, member_name, decorated_func)
        return clz
    return decorator

@log_methods()
class ClassA(object):
    def test1(self):
        print "test1"

@log_methods(timeFormat = "%m-%d-%Y %H:%M:%S")
class ClassB(ClassA):
    def test1(self):
        super(ClassB, self).test1()
        print "child test1"

    def test2(self):
        print "test2"

obj1 = ClassA()
obj2 = ClassB()

obj1.test1()
obj2.test1()
obj2.test2()

results matching ""

    No results matching ""