Python基础(十四)—装饰器 wrapper

装饰器 wrapper

先上一篇博文:详解Python装饰器

  • 装饰器的作用
    装饰器的作用就是为已经存在的函数或对象添加额外的功能。

    装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

  • 无参数的装饰器
def debug(func):
    def wrapper():
        print('[DEBUG]: enter {}()'.format(func.__name__))
        return func()
    return wrapper

@debug
def say_hello():
    print('hello!')
say_hello()
"""
[DEBUG]: enter say_hello()
hello!
"""

将 @debug 放在say_hello() 前面,相当于执行了
say_hello = debug(say_hello)
由于debug()是一个装饰器,返回了wrapper函数,所以原来的say_hello()依然存在,只是现在同名的now变量指向了新的函数,于是调用say_hello()将执行新的函数,即在debug()函数中返回的wrapper()函数。

  • 可变参数的装饰器
def debug(func):
    def wrapper(*args, **kwargs):
        print('[DEBUG]: enter {}()'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapper

@debug
def say_hello(name, something):
    print('hello!{}{}'.format(name, something))

say_hello('luozheng', '.')
"""
[DEBUG]: enter say_hello()
hello!luozheng.
"""

Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数。

高阶装饰器

  • 带参数的装饰器
    假设前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print('[{level}]: enter function {func}()'.format(
                level=level,
                func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper

@logging(level='INFO')
def say(something):
    print('say {}!'.format(something))

# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do(something):
    print('do {}...'.format(something))

if __name__ == '__main__':
    say('hello')
    do('my work')

"""
[INFO]: enter function say()
say hello!
[DEBUG]: enter function do()
do my work...
"""

当带参数的装饰器被打在某个函数上时,比如@logging(level=‘DEBUG’),它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

  • 基于类实现的装饰器
    装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。重载__call__些魔法方法一般会改变对象的内部行为,让一个类对象拥有了被调用的行为。
class Test():
    def __call__(self):
        print 'call me!'

t = Test()
t()  # call me

装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class logging(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('[DEBUG]: enter function {func}()'.format(
            func=self.func.__name__))
        return self.func(*args, **kwargs)
@logging
def say(something):
    print('say {}'.format(something))

say('love you.')
"""
[DEBUG]: enter function say()
say love you.
"""
  • 带参数的类装饰器
class logging(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):  # 接受函数
        def wrapper(*args, **kwargs):
            print('[{level}]: enter function {func}()'.format(
                level=self.level,
                func=func.__name__))
            func(*args, **kwargs)

        return wrapper  # 返回函数

@logging(level='INFO')
def say(something):
    print('say {}'.format(something))

say('love you.')
print(say.__name__)  # wrapper
  • 内置装饰器 @property
    进行类属性读写的限制,Python基础(十二)—面向对象拾遗(slots、@property、枚举类、元类)有讲解到。

装饰器的注意事项

  • 不确定的代码执行顺序
    最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。
def html_tags(tag_name):
    print('begin outer function.')
    def wrapper_(func):
        print('begin of inner wrapper function.')
        def wrapper(*args, **kwargs):
            content = func(*args, **kwargs)
            print('<{tag}>{content}</{tag}>'.format(tag=tag_name, content=content))
        print('end of inner wrapper function.')
        return wrapper
    print('end of outer function')
    return wrapper_

@html_tags('b')
def hello(name='Toby'):
    return 'Hello {}!'.format(name)

hello()
hello()
"""
begin outer function.
end of outer function
begin of inner wrapper function.
end of inner wrapper function.
<b>Hello Toby!</b>
<b>Hello Toby!</b>
"""
  • functools.wraps
    上面的一个例子:
    print(say._name_) # wrapper
    这是因为@等同于这样的写法:
    say = logging(say)
    logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。
    使用标准库里的functools.wraps,可以基本解决这个问题。
from functools import wraps
import datetime

def logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print('[DEBUG] {}: enter {}()'.format(datetime.now(), func.__name__))
        return func(*args, **kwargs)
    return wrapper

@logging
def say(something):
    """say something"""
    print('say {}!'.format(something))

print(say.__name__)  # say
print(say.__doc__) # say something
def one(func):
    print('----1----')
    def two():
        print('----2----')
        func()
    return two

def a(func):
    print('----a----')
    def b():
        print('----b----')
        func()
    return b

@one
@a
def demo():
    print('----3----')

demo()
"""
----a----
----1----
----2----
----b----
----3----
"""

个人博客:Loak 正 - 关注人工智能及互联网的个人博客
文章地址:Python基础(十四)—装饰器 wrapper

原文地址:https://www.cnblogs.com/l0zh/p/13739748.html