Python装饰器

1. 初始装饰器

1.1. 装饰器简介

装饰器(decorator)就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数

装饰器的功能特点:

  • 不修改已有函数的源代码
  • 不修改已有函数的调用方式
  • 给已有函数增加额外的功能

语法糖:

  • @xx_decorator

注意点:

  • 装饰器有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器。

1.2. 装饰器的实现原理(闭包)

def set_func(func):
    def call_func():
        print("------增加的内容------")
        func()
    return call_func


def demo():
    print("------demo------")


# 不修改已有函数的源代码
# 不修改已有函数的调用方式
# 给已有函数增加额外的功能
demo = set_func(demo)
demo()

1.3. 使用语法糖@xx_decorator之后的装饰器示例

只是把 demo = set_func(demo) 通过语法糖 @set_func 简化了!

def set_func(func):
    def call_func():
        print("------增加的内容------")
        func()
    return call_func


@set_func  # 相当于 demo = set_func(demo)
def demo():
    print("------demo------")


demo()

1.4. 一个不带参装饰器格式示例

def decorator(func):  # func:被装饰的目标函数.
    def call_func():
        func() # 执行被装饰的目标函数
    return call_func

1.5. 装饰器的执行时间

  • 直接执行当前模块时解释器遇到装饰器@xx_decorator的时候,装饰器开始执行
  • 作为模块导入时,装饰器在加载模块(解释器遇到@xx_decorator)时执行
def set_func(func):
    print("装饰器开始装饰")
    
    def call_func():
        print("------增加的内容------")
        func()
    return call_func


@set_func
def demo():
    print("------demo------")

2. 通用装饰器

装饰器的内部函数的结构和被装饰的函数结构应是一致的

  • 被装饰函数有参,那么装饰器内部函数的参数结构和被装饰函数的结构是一致的
  • 被装饰函数有返回值,那么装饰器的内部函数也需要有返回值

通用装饰器示例

装饰器使用不定长参数,无论被装饰函数是否带参都可以兼容,
装饰器直接把被装饰函数的返回值返回(无返回值使用默认的None)

def set_func(func):
    def call_func(*args, **kwargs):
        print("------增加的内容------")
        return func(*args, **kwargs)  # 这里的 * 是拆包,接收被装饰函数的返回值后返回
    return call_func


@set_func
def demo(*args, **kwargs):
    print("------demo------")
    print(f"args: {args}")
    print(f"kwargs: {kwargs}")
    return "im demo"


ret = demo(100, 200)  # 这里的ret是call_func函数的返回值
print(ret)

3. 多个装饰器一起使用

3.1. 多个装饰器使用说明

@set_func2
@set_func1
def demo():
	pass
  • 装饰器装饰顺序是由下到上(靠近被装饰函数的装饰器被先调用)。

解释器会先使用set_func1对demo函数进行装饰,然后再使用set_func2对set_func1的返回值进行装饰。

  • 被装饰函数被调用的时候,装饰器执行顺序是由上到下(靠近被装饰函数的装饰器内容最晚调用)。

执行demo函数的时候,set_func2的内容会先执行,然后再执行set_func1的内容。

3.1. 示例

def set_func1(func):
    print("set_func1执行")

    def call_func1(*args, **kwargs):
        print("------装饰器1增加的内容------")
        return func(*args, **kwargs)  # 这里的 * 是拆包,接收被装饰函数的返回值后返回
    return call_func1


def set_func2(func):
    print("set_func2执行")

    def call_func2(*args, **kwargs):
        print("------装饰器2增加的内容------")
        return func(*args, **kwargs)  # 这里的 * 是拆包,接收被装饰函数的返回值后返回
    return call_func2


@set_func2  # 对demo进行装饰(这里的demo是被set_func1装饰后的demo, 实际上是call_func1), demo = set_func2(call_func1), set_func2的返回值是 call_func2
@set_func1  # 对demo进行装饰,demo = set_func1(demo), set_func1的返回值是 call_func1
def demo(*args, **kwargs):
    print("------demo------")
    return "demo ok"


ret = demo(100, 200)  # 这里的demo实际上是执行的call_func2
print(ret)

装饰过程(由下到上):

  1. 解释器遇到@set_func2,但是@set_func2的下一行不是函数,无法被装饰,不处理继续向下执行;
  2. 解释器遇到@set_func1,@set_func1下是被装饰函数demo,set_func1开始对demo进行装饰,返回被装饰后的demo函数(即set_func1中的内部函数call_func1);
  3. 此时解释器开始使用set_func2对最新的demo函数(即set_func1中的内部函数call_func1)进行装饰,返回被装饰后的demo(即set_func2中的内部函数call_func2)。

执行过程(由上到下):

  1. 解释器遇到ret = demo(100, 200) 开始执行demo函数。此时的demo函数其实是set_func2中的内部函数call_func2;
  2. call_func2中遇到func函数,func是set_func1中的内部函数call_func1;
  3. call_func1中遇到func函数,此时的func函数就是一开始被装饰的demo;
  4. 开始执行demo函数。

结果:

set_func1执行
set_func2执行
------装饰器2增加的内容------
------装饰器1增加的内容------
------demo------
demo ok

4. 类装饰器

4.1. call方法实现

  • 想要让类的实例对象能够像函数一样进行调用,需要在类里面使用__call__()方法,把类的实例变成可调用对象(callable)
  • 类装饰器装饰函数功能在__call__()方法里面进行添加

通用类装饰器:

class Test:
    def __init__(self,  func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("装饰器功能")
        return self.func(*args, **kwargs)


@Test  # demo = Test(demo)
def demo():
    return "haha"


print(demo())

4.2. 静态方法和类方法实现(了解)

e.g. 静态方法实现

class Test:
    def __init__(self,  func):
        self.func = func

    @staticmethod
    def set_func(func):
        def call_func(*args, **kwargs):
            print("装饰器功能")
            return func(*args, **kwargs)
        return call_func


@Test.set_func  # demo = Test.set_func(demo)
def demo():
    return "haha"


print(demo())

e.g. 类方法实现

class Test:
    def __init__(self,  func):
        self.func = func

    @classmethod
    def set_func(cls, func):
        def call_func(*args, **kwargs):
            print("装饰器功能")
            return func(*args, **kwargs)
        return call_func


@Test.set_func  # demo = Test.set_func(demo)
def demo():
    return "haha"


print(demo())

5. 带参数的装饰器

  • 使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回是装饰器 ,因为 @ 符号需要配合装饰器实例使用

  • 装饰器只能接收一个参数,并且还是函数类型。

5.1. 代码示例

ddef set_params(level):
    def set_func(func):
        def call_func(*args, **kwargs):
            if level == 1:
                print("---权限级别 1---")
            elif level == 2:
                print("---权限级别 2---")
            return func(*args, **kwargs)
        return call_func
    return set_func  # 返回一个装饰器


@set_params(1)  # 先执行函数set_params保存参数,然后使用函数返回的set_func装饰器对demo1进行装饰
def demo1():
    print("---demo1---")
    return "ok"


@set_params(2)  # 先执行函数set_params保存参数,然后使用函数返回的set_func装饰器对demo2进行装饰
def demo2():
    print("---demo1---")
    return "ok"


demo1()
demo2()

5.2. 错误写法

def set_func(func, level):
    def call_func(*args, **kwargs):
        if level == 1:
            print("---权限级别 1---")
        elif level == 2:
            print("---权限级别 2---")
        return func(*args, **kwargs)
    return call_func


@set_func(1)  # 先执行函数set_params保存参数,然后使用函数返回的set_func装饰器对demo进行装饰
def demo1():
    print("---demo1---")
    return "ok"


@set_func(2)  # 先执行函数set_params保存参数,然后使用函数返回的set_func装饰器对demo进行装饰
def demo2():
    print("---demo1---")
    return "ok"


demo1()
demo2()

运行结果:

提示装饰器缺失参数,因为装饰器只能接收一个函数的引用作为参数。

Traceback (most recent call last):
  File "d:\Code\python_learn_code\video_learn_code\temp2.py", line 11, in <module>
    @set_func(1)  # 先执行函数set_params保存参数,然后使用函数返回的set_func装饰器对demo进行装饰
TypeError: set_func() missing 1 required positional argument: 'level'

6. 装饰器使用简单示例

6.1. 计算函数执行时间

import time


def set_func(func):
    def call_func(*args, **kwargs):
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print(f"函数的执行时间是: {end_time - start_time}")
        return ret
    return call_func


@set_func
def demo():
    for i in range(2):
        print(i)
        time.sleep(1)


demo()

6.2. 添加日志、权限校验功能

def set_func(func):
    def call_func(*args, **kwargs):
        print("日志、权限校验功能")
        return func(*args, **kwargs)
    return call_func


@set_func
def demo():
    print("xxx")
    return "ok"


demo()

6.3. html内容添加标签

def set_func(func):
    def call_func(*args, **kwargs):
        return f"<h1>{func(*args, **kwargs)}</h1>"
    return call_func


@set_func
def demo(body):
    return body


print(demo("xxxx"))
posted @ 2021-08-15 18:45  NFTO  阅读(38)  评论(0编辑  收藏  举报