Python进阶之强大的装饰器 Decorators (三)

2020-06-16 10:18:50 浏览数 (1)

接下来要来谈谈 functools.wraps 的功用,虽然使用装饰器可以大大的减少重复的 code,但是他有一个缺点,就是你会发现 f1 function 中的 name 和 doc 结果会怪怪的,以下的例子。

代码语言:javascript复制
def my_logging(func):
    def wrapper(*args, **kwargs):
        """my wrapper"""
        print('logging - {} is running'.format(func.__name__))
        func(*args, **kwargs)

    return wrapper


@my_logging
def f1(*args, **kwargs):
    """f1 function"""
    print("f1")

    for thing in args:
        print('hello {}'.format(thing))

    for name, value in kwargs.items():
        print('{0} = {1}'.format(name, value))


f1('twtrubiks', apple='fruit', cabbage='vegetable')
print('f1.__name__', f1.__name__)  # output -> 'wrapper'
print('f1.__doc__', f1.__doc__)  # output -> 'my wrapper'

将 f1.__name__ 以及 f1.__doc__ 印出来,竟然出现了 wrapper 的东西,( 也就是说,f1 被 wrapper 取代了,所以才显示 wrapper 的信息 ),所以为了解决这个问题,可以使用 functools.wraps 这个装饰器,将原本函数里面的信息复制到 func 之中,这样就会让 func 也有原函数 f1 的信息,方法如下。

代码语言:javascript复制
from functools import wraps


def my_logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """my wrapper"""
        print('logging - {} is running'.format(func.__name__))
        func(*args, **kwargs)

    return wrapper


@my_logging
def f1(*args, **kwargs):
    """f1 function"""
    print("f1")

    for thing in args:
        print('hello {}'.format(thing))

    for name, value in kwargs.items():
        print('{0} = {1}'.format(name, value))


f1('twtrubiks', apple='fruit', cabbage='vegetable')
print('f1.__name__', f1.__name__)  # output -> 'f1'
print('f1.__doc__', f1.__doc__)  # output -> 'f1 function'

这样子就可以正确的显示 f1 的资讯了。装饰器除了装饰 function 之外,也可以装饰 class,class decorator 主要是依赖 __call__ 的方法。

代码语言:javascript复制
class MyDecorator:
    def __init__(self, param):
        self.__param = param

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('do something before calling function {}'.format(func.__name__))
            print('self.__param', self.__param)
            func(*args, **kwargs)
            print('do something after calling function {}'.format(func.__name__))

        return wrapper


@MyDecorator('level')
def f1(*args, **kwargs):
    print('f1')
    for thing in args:
        print('hello {}'.format(thing))

    for name, value in kwargs.items():
        print('{0} = {1}'.format(name, value))


f1('twtrubiks', apple='fruit', cabbage='vegetable')

看起来稍微比较复杂,但其实就是 closures 闭包的概念。

装饰器还有一个神奇的功能,就是用装饰器来注册函数,先来看一个 flask 框架中常常用来注册 url 的方法,相信如果写过 flask 的人,一定对下面这段 code 不陌生。

代码语言:javascript复制
@app.route("/")
def index():
    pass

但是你有想过,他是怎麽办到的吗❓其实就是利用了我刚刚说的另外一个特性,用装饰器来注册函数。不太懂意思,没关系,来看一个例子。

代码语言:javascript复制
registry = dict()

def route(rule):
    def decorator(f):
        registry[rule] = f # <1>
        return f

    return decorator


@route('/')
def index():
    print('hello')
    return 'hello'

index()
print('registry:', registry) # <2>

重点在 <1> 的部分,我们只是将 rule 以及 f 注册到 registry,然后再 return 原本的 function ( 没经过任何加工,只单纯注册 url ),如果你观察 <2> 的输出,你也会发现 registry 里面有 url 的 mapping。所以装饰器除了装饰 ( 扩充 ) 原本的功能之外,还有注册函数的功用。

0 人点赞