1. 程式人生 > >[email protected]實現原理

[email protected]實現原理

@wraps作用

python中的裝飾器裝飾過的函式其實就不是函式本身了,我們可以看看下面的例子

import time
def timmer(func):
    """timmer doc"""
    def inner(*args, **kwargs):
        """inner doc"""
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("函式執行時間為 %s" % (end - start))
        return res
    return inner


@timmer
def func_test():
    """func_test doc"""
    time.sleep(2)
    return

print(func_test.__name__)  # inner
print(func_test.__doc__)  # inner doc

按我們正常的思維,func_test.__name__應該拿到的就是“func_test”,所以這個結果就印證了上面的第一句話,但是這是我們加一個@wraps,就會發現好像一切都正常了:

import time
from functools import wraps
def timmer(func):
    """timmer doc"""
    @wraps(func)
    def inner(*args, **kwargs):
        """inner doc"""
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("函式執行時間為 %s" % (end - start))
        return res
    return inner


@timmer
def func_test():
    """func_test doc"""
    time.sleep(2)
    return

print(func_test.__name__)  # func_test
print(func_test.__doc__)  # func_test doc

@wraps的實現原理

為了方便理解,我把原始碼和例子放在了一起,這樣的話我們看著會方便:

import time
from functools import partial

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)


def update_wrapper(wrapper,  # inner
                   wrapped,  # func_test
                   assigned=WRAPPER_ASSIGNMENTS,
                   updated=WRAPPER_UPDATES):
    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    print('update_wrapper 執行...')
    for attr in assigned:
        try:
            value = getattr(wrapped, attr)
        except AttributeError:
            pass
        else:
            setattr(wrapper, attr, value)
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
    # from the wrapped function when updating __dict__
    wrapper.__wrapped__ = wrapped
    # Return the wrapper so this can be used as a decorator via partial()
    print('update_wrapper 執行結束')
    return wrapper


def wraps(wrapped,
          assigned=WRAPPER_ASSIGNMENTS,
          updated=WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

       Returns a decorator that invokes update_wrapper() with the decorated
       function as the wrapper argument and the arguments to wraps() as the
       remaining arguments. Default arguments are as for update_wrapper().
       This is a convenience function to simplify applying partial() to
       update_wrapper().
    """
    print('wraps 執行...')
    print('wraps 執行結束')  # 純粹為了打印出來的結果好理解
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)


def timmer(func):
    print('timmer 執行...')

    @wraps(func)  # inner = update_wrapper的返回值
    def inner(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("函式執行時間為 %s" % (end - start))
        return res

    print('timmer 執行結束')  # 當然不是真正的結束,執行完下一行才結束
    return inner


@timmer
def func_test():
    print("func_test 執行...")
    time.sleep(2)
    print("func_test 執行結束")
    return


func_test()

"""
列印結果如下:
    timmer 執行...
    wraps 執行...
    wraps 執行結束
    update_wrapper 執行...
    update_wrapper 執行結束
    timmer 執行結束
    func_test 執行...
    func_test 執行結束
    函式執行時間為 2.0000197887420654


從列印的結果我們可以看出,@語法會在函式定義或者說模組初始化階段(可能稱呼不對,以後回來改)就執行了
"""

上面的例子中我加了很多列印,主要是為了提醒一下在func_test()函式執行之前,@語法已經執行了。

其實原理很簡單,用了一個偏函式,去執行update_wrapper,真正起作用的也是這個函式,func_test執行之前,update_wrapper函式就會把inner函式的好多屬性(示例中WRAPPER_ASSIGNMENTS,WRAPPER_UPDATES指向的屬性 ,還有__wrapped__屬性)全部其換成func_test的屬性。