python-装饰器

目录

一、 装饰器介绍

1. 为何要用装饰器

2. 什么是装饰器

二、 装饰器的实现

1. 无参装饰器的实现

1.1. 装饰器的简易版本

1.2. 使用语法糖实现

1.3. 装饰器模板

1.4. 双层语法糖

1.5. 多层语法糖

1.6. 装饰器修复技术(了解)

1.7. 装饰器之登录认证功能

2. 有参装饰器的实现


一、 装饰器介绍

1. 为何要用装饰器

  • Python 中的装饰器是一种语法糖,可以在运行时,动态的给函数或类添加功能。
  • 装饰器本质上是一个函数,使用 @ + 函数名就是可实现绑定给函数的第二个功能 。
  • 将一些通用的、特定函数的功能抽象成一个装饰器,可以重复利用这些功能

2. 什么是装饰器

  • “装饰”代指为被装饰对象添加新的功能,“器”代指器具/工具
  • 装饰器的作用:就是在不修改被装饰对象源代码和调用方式的前提下为被装饰对象添加额外的功能
  • 装饰器使用场景:插入日志、性能测试、事务处理、缓存、权限校验
  • 可以调用的有:函数、方法、类
  • 函数装饰器分为:无参装饰器有参装饰,二者都是使用都是需要【名称空间+函数嵌套+闭包+函数对象的组合知识
  • 使用“@”符号定义装饰器,前提是需要有一个函数作为工具然后被“@”装饰到其他函数头上,为这个函数添加功能

二、 装饰器的实现

1. 无参装饰器的实现

1.1. 装饰器的简易版本

第一种:直接使用函数对象来实现
import time

print(time.time()) # 1685333745.048791 #1685333745叫时间戳:它是从 1970 年- 1-1 开始,到当前时间的秒数

def index():
    time.sleep(3)  # 2.调用 time.sleep(3) 函数,暂停程序的执行 3 秒钟。
    print('Welcome to the index page')  # 3.打印一条欢迎消息 "Welcome to the index page"。
    return 200  # 4.返回状态码 200。


res = index  # 1.执行 index() 函数。
# 5.函数执行完毕,程序继续往下执行。

"""如果不懂函数体的代码该如何计算程序执行了多少秒呢"""

start_time = time.time()  # 函数未执行前起始计算时间
res()  # 函数执行
stop_time = time.time()  # 函数执行之后结束时间
print('run time is %s' % (stop_time - start_time))#结束时间减去开始时间等于函数执行时间
"""考虑到还有可能要统计其他函数的执行时间,所以还要使用函数参数的形式"""

*******************************************************************************************************************

第二种:使用参数的形式传入
def wrapper(func):  # 通过函数接收外部的值
    start_time = time.time()
    func()
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))


wrapper(index)  # 如果考虑到还有可能要统计其他函数的执行时间就在下方继续wrapper(其他函数)
"""但是使用参数的话就改变了调用方式,违反了不能修改被装饰对象调用方式的原则,所以我们使用闭包函数,将值报给函数"""

*******************************************************************************************************************

第三种:使用闭包函数的形式
def timer(func):
    def wrapper():
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res

    return wrapper


asd = timer(index)
asd()

"""以上三种方法我们就实现了无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常"""



def home(name):
    time.sleep(5)
    print('Welcome to the home page', name)


home = timer(home)
home('egon')

# 抛出异常:TypeError: wrapper() takes 0 positional arguments but 1 was given
""" 因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合,于是修正装饰器timer如下 """
第四种:装饰器的进阶版本解决参数问题

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res

    return wrapper

1.2. 使用语法糖实现

import time
def timer(func): #定义一个名为 timer() 的装饰器函数,它接受一个参数 func这个 func 就是即将要被修饰的函数
    def wrapper(*args, **kwargs): #在 timer() 函数内部,定义一个名为 wrapper() 的闭包函数
        start_time = time.time() #在调用 wrapper() 函数时,它会根据传入的参数来调用被修饰的函数 func,并在函数执行前后记录时间
        res = func(*args, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res#同时输出函数执行时间。最后,它将 func 函数的返回值返回

    return wrapper


@timer #使用装饰器语法来对函数进行装饰,在函数定义前加上 @timer 这样的语句
def index():
    time.sleep(1)
    print('Welcome to the index page')
    return 200

index()#调用 index() 函数时,会自动将其转换成 timer(index)() 这样的形式,将 index 函数作为参数传递给 timer() 函数,并将返回值再次作为函数调用。由于 timer() 函数返回了一个闭包函数 wrapper(),所以最终的函数调用结果就是执行了闭包函数 wrapper()

"""在 index() 函数内部,我们调用了 time.sleep(1) 函数来模拟函数执行的耗时操作。然后,我们输出了欢迎信息,并返回了状态码 200。
执行 index() 函数时,由于它被装饰器修饰了,所以实际上是执行了闭包函数 wrapper()。在 wrapper() 函数中,我们记录了函数执行前后的时间,并输出了运行时间。最后,我们将 index() 函数的返回值 200 返回给调用者。
"""

1.3. 装饰器模板

  • 语法糖的书写规范:紧贴在被装饰对象的上方书写,语法糖语被装饰对象之间不可以有代码
  • 语法糖的内部原理:语法糖会自动把被装饰对象的名字当成参数传给装饰器函数调用
#定义一个装饰器函数,它接受一个函数作为参数并返回一个新的函数
def decorator(func):
    # 定义一个内部函数,它接受任意数量和类型的位置参数和关键字参数
    def wrapper(*args, **kwargs):
        # 在调用被装饰函数之前执行的代码
        print("Before calling the function")
        # 调用被装饰函数,并保存其返回值
        result = func(*args, **kwargs)
        # 在调用被装饰函数之后执行的代码
        print("After calling the function")
        # 返回被装饰函数的返回值
        return result
    # 返回内部函数,以便它可以被调用
    return wrapper

# 定义一个函数,并使用装饰器来增加其功能
@decorator  #my_function = decorator(my_function)my_function是decorator函数的返回值(内部函数的函数名)
def my_function(a, b):
    # 模拟一个运算,并返回结果
    result = a + b
    return result

# 调用被装饰函数
print(my_function(1, 2))

执行结果:当调用 my_function(1, 2) 时,输出结果如下:

Before calling the function
After calling the function
3

1.4. 双层语法糖

import time


def login_auth(func):
    def auth(*args, **kwargs):
        username = input('username:').strip()
        password = input('password:').strip()
        if username == 'kevin' and password == '123':
            print('登录成功')
            res = func(*args, **kwargs)
            return res

        else:
            print('用户名密码错误')

    return auth


def all_time(func1):
    def time1(*args, **kwargs):
        start_time = time.time()
        res = func1(*args, **kwargs)
        stop_time = time.time()
        print('功能执行了%s秒' % (stop_time - start_time))
        return res

    return time1


@all_time  # index = all_time(all_time内部的 time1 的函数名) 当装饰器执行到最后一层这个变量就使用跟最初装饰器同名的函数名同名
@login_auth  # all_time内部的 time1 的函数名 = login_auth(index) 语法糖会把被装饰对象的函数名当成第一个参数传给装饰器的第一个参数
def index():
    print('执行完成')


index()  # 最后一层就是 auth()

1.5. 多层语法糖

def outter1(func1):
    print('加载了outter1')

    def wrapper1(*args, **kwargs):
        print('执行了wrapper1')
        res1 = func1(*args, **kwargs)
        return res1

    return wrapper1


def outter2(func2):
    print('加载了outter2')

    def wrapper2(*args, **kwargs):
        print('执行了wrapper2')
        res2 = func2(*args, **kwargs)
        return res2

    return wrapper2


def outter3(func3):
    print('加载了outter3')

    def wrapper3(*args, **kwargs):
        print('执行了wrapper3')
        res3 = func3(*args, **kwargs)
        return res3

    return wrapper3


@outter1  # index(outter1的内层函数名,一般最后一层我们使用跟功能函数同名的函数名 index) = outter1(wrapper2) ---------------wrapper1
@outter2  # wrapper2(outter2的内层函数名) = outter2(wrapper3)
@outter3  # wrapper3(outter3的内层函数名) =  outter3(index)
"""
只要加了语法糖,装饰器一定会执行,不调用被装饰的函数也会执行装饰器
执行装饰器是从下往上执行,执行到最后一个装饰器之后,会把最后一个装饰器的返回值的变量名语与被装饰对象的函数名同名
语法糖不会在没有调用装饰对象之前,是不会执行内部函数的
"""
def index():
    print('from index')


index()#只要不调用被装饰的函数就不会执行装饰器内层函数

执行顺序分析:
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
执行顺序原理解释如下:

1.定义 outter3 函数时,先打印出 "加载了outter3";
2.进入 outter2 函数,先打印出 "加载了outter2";
3.在 outter2 函数中创建了 wrapper2 函数并返回,但并不执行 wrapper2 函数,此时将 wrapper2 函数作为参数传递给 @outter1 装饰器;
4.进入 outter1 函数,先打印出 "加载了outter1";
5.在 outter1 中将 wrapper2 函数作为参数传递给 outter1 的内部函数 wrapper1,并返回 wrapper1 函数,此时 index 函数被全局变量所指向;
6.当调用 index() 函数时,实际上是调用了经过装饰器增强后的 wrapper1 函数;
7.调用 wrapper1 函数,首先打印出 "执行了wrapper1",然后调用 wrapper2 函数;
8.调用 wrapper2 函数,首先打印出 "执行了wrapper2",然后调用 wrapper3 函数;
9.调用 wrapper3 函数,首先打印出 "执行了wrapper3",然后执行函数体中的 print('from index') 语句;
最终输出函数的执行结果 "from index"。

1.6. 装饰器修复技术(了解)

  • 装饰器通常会将一个函数替换成另一个函数,修改函数的属性,或者在函数周围添加一些代码来实现某些功能,这可能会导致函数元数据信息的丢失,比如函数名、文档字符串、参数列表等等,为了避免这种情况,可以使用 wraps 装饰器来修饰装饰器本身,从而确保被修饰的函数保留了其原有的元数据信息不变。
  • wraps 装饰器本身也是一个装饰器,它接受一个函数作为参数,并返回一个包装函数。wraps 装饰器会将包装函数的名称、文档字符串、参数列表等元数据信息更新为原函数的元数据信息,从而确保被修饰的函数可以正确地保留原有的元数据信息。
  • 使用方法:导入 from functools import wraps在装饰器的外部与内部函数之间定义 @wraps(func)
    • wraps 装饰器确保了被装饰的函数 say_hello 保留了其原本的元数据信息,包括函数名和文档字符串。这样,即使在嵌套多个装饰器的情况下,也能够正确地保留函数的属性信息。
from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('Before the function is called.')
        result = func(*args, **kwargs)
        print('After the function is called.')
        return result
    return wrapper

@my_decorator
def say_hello(name):
    """A function that says hello"""
    print(f'Hello, {name}!')

print(say_hello.__name__)   # 输出 'say_hello'
print(say_hello.__doc__)    # 输出 'A function that says hello'

1.7. 装饰器之登录认证功能

is_login = {'is_login':False} #判断如果有一个函数成功等候后面的函数就不需要登录了
def login_auth(func):
    def login(*args, **kwargs):
        username = input('username:').strip()
        password = input('password:').strip()
        if username == 'kevin' and password == '123':
            print('登录成功')

            res = func(*args, **kwargs)
            
            is_login['is_login'] = True#只要is_login为 True  就不会登录
            return res
        else:
            print('用户名或者密码错误')
    return login

@login_auth
def user_login():
    print('认证成功')
res = user_login
res()

2. 有参装饰器的实现

  • 有参装饰器本质上就是在普通装饰器头上再嵌套一层函数用来传递参数
  • 当我们使用最外层的函数传递参数是,语法糖后面的括号中也需要传递相应的参数
#定义一个带参数的装饰器函数 repeat(),它接受一个整数类型的参数 count,表示被修饰函数需要重复执行的次数:
def repeat(count):
    def decorator_func(func):
        def wrapper_func(*args, **kwargs):
            for i in range(count):
                res = func(*args, **kwargs)
            return res
        return wrapper_func
    return decorator_func

    """
    在 repeat() 函数内部,定义了一个闭包函数 decorator_func(),用于接受被修饰函数 func。在 decorator_func() 函数内部,再定义了一个闭包函数 wrapper_func(),用于实现装饰器的具体功能。
    在 wrapper_func() 函数内部,使用一个循环来控制被修饰函数的执行次数。在每次循环中,我们尝试执行被修饰函数,并返回其结果。最终,我们将 wrapper_func() 函数作为修饰器的返回值。
    """

@repeat(3)
def greet(name):
    print("Hello, %s!" % name)

"""
当我们执行 greet('Alice') 函数时,它实际上已经被修饰成了 repeat(3)(greet)('Alice') 这样的形式,即先将 greet 函数传递给 decorator_func(),再将返回的 wrapper_func 函数作为结果返回,并在其基础上执行函数 greet('Alice')。
由于装饰器 repeat() 是一个带参数的装饰器,所以要在装饰器名称后面加上括号并传入参数值,来指定装饰器的参数值。在这个例子中,我们使用 @repeat(3) 来指定函数 greet() 需要重复执行 3 次。
"""
res1 = greet

res()
Hello, Alice!
Hello, Alice!
Hello, Alice!