Python装饰器(@wraps)及闭包用例详解

一、装饰器简介

1. 装饰器是什么?

概括地讲,装饰器的作用就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能

装饰器经常用于比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

有了装饰器,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用

2. 为什么用装饰器?

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。

在软件设计完成后,不想改部分源码又想添加新功能,就用到了装饰器。

二、装饰器的实现

函数装饰器分为:无参装饰器和有参装饰两种,二者的实现原理都是“函数嵌套+闭包+函数对象”的组合使用。

1. 闭包

闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数

这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

def print_msg():  # print_msg是外围函数
    msg = "I'm closure"

    def printer():  # printer是嵌套函数
        print(msg)
    return printer

closure = print_msg()  # 这里获得的就是一个闭包
closure()  # 输出 I'm closure

msg是一个局部变量,在print_msg函数执行之后就不会存在了。

但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。

2. 装饰器语法糖

语法糖(Syntactic sugar),也译为糖衣语法,指计算机语言中添加的某种语法。

这种语法对语言的功能并没有影响,但是更方便程序员使用。

通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

@ 符号是装饰器的语法糖。它放在一个函数开始定义的地方(头顶),和这个函数绑定在一起。

在我们调用这个函数的时候,会先将这个函数做为参数传入它头顶,即装饰器里。

3. 时间计时器

以下用装饰器来实现计算一个函数的执行时长,让函数睡眠3秒。

# 这是装饰函数
def timer(func):
    def wrapper(*args, **kw):
        start_time = time.time()
        func(*args, **kw)  # 这是函数真正执行的地方
        stop_time = time.time()
        cost_time = stop_time - start_time
        print("花费时间:{}秒".format(cost_time))
    return wrapper

import time

@timer
def want_sleep(sleep_time):
    time.sleep(sleep_time)

want_sleep(3)

4. 装饰器中@wraps作用

装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变)。

为了不影响,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用。

写一个装饰器的时候,最好在实现之前加上functools中的wraps,它能保留原有函数的名称和文档字符串(DocStrings)。

文档字符串用于解释文档程序,帮助程序文档更加简单易懂。

可以在函数体的第一行使用一对三个单引号 ‘’’ 或者一对三个双引号 “”" 来定义文档字符串。

使用 doc(注意双下划线)调用函数中的文档字符串属性。

  1. 不使用@wraps装饰器
def decorator(func):
    """this is decorator __doc__"""

    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__:  ", test.__doc__)

运行结果:
name: wrapper
doc: this is wrapper doc

分析:
对test()方法进行装饰时候,实际上是

test = decorator(test)

返回的是wrapper方法的引用,也就是让test指向了wrapper方法,所以调用test.name, 实际上是wrapper.name

这造成后面查找该方法的名字和注释时得到装饰器内嵌函数的名字和注释

  1. 使用@wraps装饰器解决这个问题
from functools import wraps


def decorator(func):
    """this is decorator __doc__"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        """this is wrapper __doc__"""
        print("this is wrapper method")
        return func(*args, **kwargs)
    return wrapper

@decorator
def test():
    """this is test __doc__"""
    print("this is test method")

print("__name__: ", test.__name__)
print("__doc__:  ", test.__doc__)

运行结果:
name: test
doc: this is test doc

5. 装饰器顺序

一个函数可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于:

f = a(b(c(f)))

三、装饰器的参数

1. 无参类装饰器

装饰器不仅可以是函数,还可以是类。

基于类装饰器的实现,必须实现 call 和 __init__两个内置函数。

当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

init :接收被装饰函数
call :实现装饰逻辑。

class logger(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..." \
              .format(func=self.func.__name__))
        return self.func(*args, **kwargs)
        
@logger
def say(something):
    print("say {}!".format(something))
say("hello")

运行结果:
[INFO]: the function say() is running…
say hello!

2. 有参类装饰器

上面不带参数的例子只能打印INFO级别的日志。

当需要打印DEBUG、WARNING等级别的日志时就需要给类装饰器传入参数,给这个函数指定级别了。

带参数和不带参数的类装饰器有很大的不同。

init :不再接收被装饰函数,而是接收传入参数。
call :接收被装饰函数,实现装饰逻辑。

class logger(object):
  def __init__(self, level='INFO'):
    self.level = level

  def __call__(self, func): # 接受函数
    def wrapper(*args, **kwargs):
      print("[{level}]: the function {func}() is running..."\
        .format(level=self.level, func=func.__name__))
      func(*args, **kwargs)
    return wrapper #返回函数

@logger(level='WARNING')
def say(something):
  print("say {}!".format(something))

say("hello")

指定WARNING级别后的运行结果:
[WARNING]: the function say() is running…
say hello!

3. 无参装饰器模板

其中wrapper功能:

1、调用原函数
2、为其增加新功能

def template(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
        
    return wrapper

4. 有参装饰器模板

def 有参装饰器(x,y,z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter

@有参装饰器(1,y=2,z=3)
def 被装饰对象():
    pass

结语

以上是Python装饰器详解,希望对大家有所帮助。如果大家有任何疑问请给我留言,我会尽快回复大家。在此也非常感谢大家对CSDN的支持!
posted @ 2022-02-06 18:51  Hardworking666  阅读(1183)  评论(0编辑  收藏  举报