1. 程式人生 > >通俗易懂的分析——python裝飾器之@functools.warps

通俗易懂的分析——python裝飾器之@functools.warps

首先,寶寶覺著網上沒有比我這還透徹的@functools.wraps分析了~~~害羞自戀ing……

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging
@logged
def f(x):
   """does some math"""
   return x + x * x

it's exactly the same as
saying def f(x): """does some math""" return x + x * x f = logged(f)

這時(劃重點!),your function f is replaced with the function with_logging.

print f.__name__ #with_logging
print f.__doc__  #什麼也沒有,因為with_logging沒有doc,f才有doc

因為f方法的name doc args也變了
簡言之:使用裝飾器時,原函式會損失一些資訊,因為指向裝飾器中的函式。(具體解釋看後邊的原理~)
所以,我們需要使用@functools.wraps,使用方法如step2
step2:

import functools
def logged(func):
    @functools.wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x
print f.__name__ #'f'
print
f.__doc__ #'does some math'

————————————————————————————
原理如下:

#...functools.py檔案...
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    #...這時wrapper已經有了wrapped的assignments和__dict__
    return wrapper 
def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    #...其實是用partial對update_wrapper封裝,partial後邊會舉例子
    return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

這樣 我們再來看stackoverflow裡面的例子(我們展開step2中的@functools.wraps)
展開1(@functools.wraps(func)轉換成@_temp,裝飾器的語法糖替換)

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)

    _temp = functools.wraps(func)
    with_logging = _temp(with_logging)

    return with_logging

展開2(wraps(func)展開)

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)

    _temp = functools.partial(update_wrapper, wrapped=func, assigned=assigned, updated=updated)
    #1、先了解partial函式
    #這個函式意思是:我有個函式add_three_number(a,b,c)(三個數相加求和),a=2和b=5固定不變,c可變
    #但是我不想每次呼叫都寫add_three_number(a=2,b=5,c=x),我只想寫sum_number(x)
    #這樣就可以用partial函式封裝一下,sum_number=partial(add_three_number,a=2,b=5),每次呼叫sum_number(x)就好了
    #partial封裝的update_wrapper函式賦值給了_temp,這時update_wrapper函式只有wrapper是可變的

    with_logging = _temp(with_logging)
    #2、_temp(with_logging)相當於把update_wrapper函式中的wrapper變數補齊~

    return with_logging

展開3(partial展開)

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)


    with_logging = functools.update_wrapper(with_logging, func, assigned=assigned, updated=updated)
    return with_logging

之後,update_wrapper函式將func(也就是f)的WRAPPER_ASSIGNMENTS = (‘module‘, ‘name‘, ‘doc‘) 和WRAPPER_UPDATES = (‘dict‘,)賦給返回函式(with_logging)

原理done
——————————————————————
哦哈哈哈~開森

(總結,敲黑板!)
So,f = logged(f)之後(等同於f加裝飾器@logged),f=with_logging,
情況1:裝飾器中沒有用@functools.wraps(func)。f直接指向with_logging方法就會出現下邊的情況:

print f.__name__ #with_logging
print f.__doc__  #什麼也沒有,因為with_logging沒有doc,f才有doc

情況2:裝飾器中用了@functools.wraps(func)。按原理中的解釋,一步一步,用update_wrapper方法把WRAPPER_ASSIGNMENTS和WRAPPER_UPDATES賦給with_logging。最後f還是指向with_logging方法,但with_logging方法多了f的屬性呀,所以出現正確結果:

print f.__name__ #'f'
print f.__doc__  #'does some math'

(造成你以為還在呼叫f方法的假象= =!)