请稍侯

functools中@waps的使用

09 March 2015

functools中@waps

使用装饰器的一个副作用就是,装饰之后函数丢失了它本来的__name__,__doc____module__属性。

def my_decorator(f):
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example():
    """Docstring"""
    print 'Called example function'

example()
print example.__name__
print example.__doc__

输出的结果是:

>>>Calling decorated function
>>>Called example function
>>>wrapper
>>>None

使用functools.wraps中@wraps装饰器可以让丢失的函数属性__name__,__doc__,__module__恢复

from functools import wraps
def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example():
    """Docstring"""
    print 'Called example function'

example()
print example.__name__
print example.__doc__

加入@wraps的输出结果是:

>>>Calling decorated function
>>>Called example function
>>>example
>>>Docstring

@wraps实现原理

上面的例子,函数的调用顺序是:

example() -> my_decorator(example)() -> 
wraps(example, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES)(wrapper)()

查看wraps的实现代码是:

  def wraps(wrapped, 
            assigned = WRAPPER_ASSIGNMENTS, 
            updated = WRAPPER_UPDATES):

    return partial(update_wrapper, 
                   wrapped=wrapped, 
                   assigned=assigned, 
                   updated=updated)

即:

partial(update_wrapper, wrapped=example, assigned=assigned, updated=updated)(wrapper)()

注意:调用函数partial之后,wrapped=example在函数partial参数keywords中

functools.partial的大概的Python实现是:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

因此newfunc.func = update_wrappernewfunc.args = example

函数成为:newfunc(wrapper),因此newfunc函数最终执行的是update_wrapper(wrapper, wrapped = example,...) 再看update_wrapper的实现.

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, {}))
    return wrapper

partial函数的使用

Python 中有个 int([x[,base]]) 函数,作用是把字符串转换为一个普通的整型。 如果要把所有输入的二进制数转为整型,那么就要这样写 int('11', base=2)。这样写起来貌似不太方便,那么我们就能用 partial 来实现值传递一个参数就能转换二进制数转为整型的方法。

from functools import partial
int2 = partial(int, base=2)

print int2('11') # 3
print int2('101') # 5

附录:完整的实现(没有使用库函数)

def partial_t(func, *args, **keywords):
    print "keywords is %s" % keywords
    print args
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        print fargs
        print "newkeywords is %s" % newkeywords
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

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, {}))
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    return partial_t(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper

@my_decorator
def example():
    """Docstring"""
    print 'Called example function'

example()
print example.__name__
print example.__doc__

输出结果:

keywords is {'wrapped': <function example at 0x0000000002649CF8>, 'assigned': ('__module__', '__name__', '__doc__'), 'updated': ('__dict__',)}
() #args
(<function wrapper at 0x0000000002649DD8>,) #fargs
newkeywords is {'wrapped': <function example at 0x0000000002649CF8>, 'assigned': ('__module__', '__name__', '__doc__'), 'updated': ('__dict__',)}
Calling decorated function
Called example function
example
Docstring