装饰器
【一】装饰器介绍
装饰器的由来
- 软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着对象一旦设计完成,就可以独立完成其工作,而不要对其进行修改。
- 软件包含的所有功能的源代码以及调用方式,都应该避免修改,否则一旦改错,则极有可能产生连锁反应,最终导致程序崩溃
- 而对于上线后的软件,新需求或者变化又层出不穷,我们必须为程序提供扩展的可能性,这就用到了装饰器。
装饰器的基本定义
装饰器是 Python 中一种用于修改或扩展函数行为的高级技术。装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数,通常用于在不修改原始函数代码的情况下添加新的功能或行为。
装饰器的语法使用 @decorator
这样的语法糖,将装饰器应用到函数上。在应用装饰器后,原始函数会被包裹或修改,从而获得额外的功能。
下面是一个简单的装饰器示例:
| def my_decorator(func): |
| def wrapper(): |
| print("Something is happening before the function is called.") |
| func() |
| print("Something is happening after the function is called.") |
| return wrapper |
| |
| @my_decorator |
| def say_hello(): |
| print("Hello!") |
| |
| |
| say_hello() |
在这个例子中,my_decorator
是一个装饰器函数,它接受一个函数 func
作为参数,并返回一个新的函数 wrapper
。wrapper
函数在调用原始函数 func
前后打印一些信息。
通过使用 @my_decorator
语法糖,我们将 say_hello
函数传递给装饰器,实际上相当于 say_hello = my_decorator(say_hello)
。当调用 say_hello()
时,实际上调用了被装饰后的 wrapper
函数。
装饰器的特点:
- 接受函数作为参数: 装饰器是一个接受函数作为输入的函数。
- 返回新函数: 装饰器通常返回一个新的函数,该函数通常包装了原始函数,并添加了额外的功能。
- 使用
@
语法: 使用 @decorator
语法糖可以方便地应用装饰器。
常见应用场景:
- 日志记录: 记录函数的调用信息,参数和返回值。
- 性能分析: 计算函数执行时间等性能指标。
- 权限检查: 检查用户是否有权限执行特定函数。
- 缓存: 缓存函数的输出,避免重复计算。
- 异常处理: 包装函数,捕获异常并进行处理。
【二】装饰器的分类
【0】调用装饰器
| def 装饰器(func): |
| def inner(): |
| 函数体内容 |
| return 返回的内容 |
| return inner |
| def 函数体(): |
| 函数内容 |
| 函数名 = 装饰器(函数名) |
| 函数名() |
【1】无参装饰器
- 无参装饰器和有参装饰器的实现原理一样,都是’函数嵌套+闭包+函数对象’的组合使用的产物。
| |
| def wrapper(func): |
| def inner(): |
| |
| '''...''' |
| |
| result = func() |
| |
| '''...''' |
| return result |
| return inner |
| |
| |
| |
| def wrapper(func): |
| def inner(*args, **kwargs): |
| |
| '''...''' |
| |
| result = func(*args, **kwargs) |
| |
| '''...''' |
| return result |
| return inner |
【2】有参装饰器
有参数的装饰器是指在装饰器外部可以传递一些参数,以影响装饰器的行为。这样的装饰器通常有两层嵌套,外层接收参数,内层接收函数。下面是一个简单的有参装饰器的例子:
| def my_decorator(param): |
| def decorator(func): |
| def wrapper(*args, **kwargs): |
| print(f"Decorator received parameter: {param}") |
| |
| |
| result = func(*args, **kwargs) |
| print(f"Function {func.__name__} was called.") |
| return result |
| |
| return wrapper |
| |
| return decorator |
| |
| |
| |
| |
| def say_hello(): |
| print("Hello!") |
| |
| a=my_decorator(param='最外层语法糖的内容') |
| say_hello = a(say_hello) |
| say_hello() |
| ''' |
| Decorator received parameter: 最外层语法糖的内容 |
| Hello! |
| Function say_hello was called |
| ''' |
在这个例子中,my_decorator
是一个有参装饰器。它实际上返回一个装饰器函数 decorator
,而 decorator
函数则返回最终的包装函数 wrapper
。外部传递的参数 param
影响了装饰器内部的行为。
当使用 @my_decorator("Hello, I'm a parameter.")
语法糖来装饰 say_hello
函数时,实际上相当于执行了 say_hello = my_decorator("Hello, I'm a parameter.")(say_hello)
。
运行 say_hello()
时,将执行 wrapper
函数,同时输出装饰器接收到的参数以及函数的调用信息。
有参装饰器非常灵活,可以根据外部传递的参数来定制装饰器的行为,使得装饰器更具通用性和可配置性。
【三】装饰器语法糖
【0】语法糖的概念
- 语法糖(Syntactic Sugar)是一种编程语言的语法结构,它并不提供新的功能,而是提供了一种更方便、更易读、更简洁的语法形式。这种语法形式并不改变语言的表达能力,但使得代码更加易于理解和书写。
- 在编程中,语法糖的存在旨在使得代码更加简洁,减少冗余,提高可读性。这些语法糖通常是通过编译器或解释器在底层转化为更基础的语法结构,而不是引入新的语法规则。
以下是一些常见的 Python 语法糖的例子:
-
列表推导式:
| |
| squares = [] |
| for x in range(10): |
| squares.append(x**2) |
| |
| |
| squares = [x**2 for x in range(10)] |
-
字典推导式:
| |
| squares_dict = {} |
| for x in range(10): |
| squares_dict[x] = x**2 |
| |
| |
| squares_dict = {x: x**2 for x in range(10)} |
-
装饰器:
| |
| def my_decorator(func): |
| def wrapper(): |
| print("Something is happening before the function is called.") |
| func() |
| print("Something is happening after the function is called.") |
| return wrapper |
| |
| def say_hello(): |
| print("Hello!") |
| |
| say_hello = my_decorator(say_hello) |
| |
| |
| @my_decorator |
| def say_hello(): |
| print("Hello!") |
这些例子展示了语法糖的概念:它们提供了更简洁和直观的方式来表达相同的逻辑,使得代码更加优雅和易读。
【1】无参语法糖@wrapper
和有参语法糖@wrapper()
| import time |
| |
| |
| def home(name): |
| time.sleep(5) |
| print('Welcome to the home page', name) |
| |
| |
| |
| def wrapper(func): |
| |
| def inner(*args, **kwargs): |
| start_time = time.time() |
| res = func(*args, **kwargs) |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
| |
| return inner |
| |
| |
| |
| |
| home = wrapper(home) |
| |
| |
| home("user") |
| |
| |
| |
| @wrapper |
| def index(): |
| time.sleep(5) |
| print('Welcome to the index page') |
| |
| |
| index() |
- 有参语法糖
- 有参语法糖可以理解为先执行语法糖最外层的函数,然后再添加
@
形成语法糖
@my_decorator(param)
就等于my_decorator(param) == 返回的值decorator
-----@decorator
- 有参语法糖的作用就是将参数传入到装饰器内作为一个局部变量交由装饰器内部使用
| def my_decorator(param): |
| def decorator(func): |
| def wrapper(*args, **kwargs): |
| print(f"Decorator received parameter: {param}") |
| |
| |
| result = func(*args, **kwargs) |
| print(f"Function {func.__name__} was called.") |
| return result |
| |
| return wrapper |
| |
| return decorator |
| |
| |
| @my_decorator(param="Hello, I'm a parameter.") |
| def say_hello(): |
| print("Hello!") |
| |
| |
| |
| print(say_hello()) |
| |
| |
| |
| |
【2】语法糖嵌套

| @check_login |
| @check_balance |
| def get_balance(*args, **kwargs): |
| |
| def desc_a(func): |
| print(f'111') |
| def inner(*args, **kwargs): |
| print(f'222') |
| res = func(*args, **kwargs) |
| print(f'333') |
| return res |
| return inner |
| |
| def desc_b(func): |
| print(f'444') |
| def inner(*args, **kwargs): |
| print(f'555') |
| res = func(*args, **kwargs) |
| print(f'666') |
| return res |
| return inner |
| @desc_b |
| @desc_a |
| def add(): |
| print(f'777') |
| add() |
| |
| '''多层语法糖相当于''' |
| add = desc_a(add) |
| add = desc_b(add) |
| add() |
【3】多层语法糖练习1
| |
| user_data = {'username': 'dream', 'password': '521'} |
| bank_data = {'dream': {'pay_pwd': '521', 'balance': 1000}} |
| |
| |
| def check_login(func): |
| def inner(*args, **kwargs): |
| username = input("请输入用户名:").strip() |
| password = input("请输入密码:").strip() |
| if user_data['username'] == username: |
| if user_data['password'] == password: |
| return func(username=username) |
| else: |
| return f"密码错误!" |
| else: |
| return f'用户名错误!' |
| |
| return inner |
| |
| |
| def check_balance(func): |
| def inner(*args, **kwargs): |
| pay_pwd = input("请输入支付密码:").strip() |
| if bank_data[kwargs.get('username')]['pay_pwd'] == pay_pwd: |
| balance = input("请输入取款金额:").strip() |
| if not balance.isdigit(): |
| return f"金额必须是数字" |
| balance = int(balance) |
| if bank_data[kwargs.get('username')]['balance'] < balance: |
| return f"你哪有那么多钱!" |
| else: |
| return func(username=[kwargs.get('username')], balance=balance) |
| else: |
| return f'支付密码错误!' |
| |
| return inner |
| |
| |
| @check_login |
| @check_balance |
| def get_balance(*args, **kwargs): |
| new_balance = bank_data[kwargs.get('username')]['balance'] - kwargs.get('balance') |
| bank_data[kwargs.get('username')]['balance']=new_balance |
| return f"取款成功,已提款{kwargs.get('balance')}现在你的余额为{new_balance}" |
| |
| |
| print(get_balance()) |
【4】多层语法糖练习2(三层语法糖打印顺序)
| |
| |
| 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 |
| @outter2 |
| @outter3 |
| def index(): |
| print('from index') |
| index() |
| |
| ''' |
| 加载了outter3 |
| 加载了outter2 |
| 加载了outter1 |
| 执行了wrapper1 |
| 执行了wrapper2 |
| 执行了wrapper3 |
| from index |
| ''' |

【四】装饰器形成的思路
【1】引入[为函数添加计时的功能]
| import time |
| |
| def index(): |
| time.sleep(3) |
| print('Welcome to the index page’) |
| return 200 |
| |
| index() #函数执行 |
| |
【2】直接计时
- 遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样
| import time |
| |
| |
| def index(): |
| time.sleep(3) |
| print("Welcome to the index page") |
| return 200 |
| |
| |
| start_time = time.time() |
| index() |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
【3】函数作为参数
- 考虑到还有可能要统计其他函数的执行时间
- 于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用
- 我们可以使用参数的形式传入
| import time |
| |
| |
| def index(): |
| time.sleep(3) |
| print("Welcome to the index page") |
| return 200 |
| |
| |
| def wrapper(func): |
| start_time = time.time() |
| res = func() |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
| return res |
| |
| |
| wrapper(index) |
| wrapper(index) |
| wrapper(其他函数) |
【4】将值包给函数
| import time |
| |
| |
| def index(): |
| time.sleep(3) |
| print("Welcome to the index page") |
| return 200 |
| |
| |
| 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 inner |
| |
| |
| wrapper = timer(index) |
| wrapper() |
- 这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能
- 只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index
| index=timer(index) |
| index() |
- 至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。
【5】问题:带参数会报错
- 但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常
| import time |
| |
| |
| def home(name): |
| time.sleep(5) |
| print('Welcome to the home page', name) |
| |
| |
| def wrapper(func): |
| def inner(): |
| start_time = time.time() |
| res = func() |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
| |
| return inner |
| |
| |
| home = wrapper(home) |
| home() |
| ''' |
| Traceback (most recent call last): |
| File "E:\PythonProjects\def_func.py", line 322, in <module> |
| home() |
| File "E:\PythonProjects\def_func.py", line 314, in inner |
| res = func() |
| TypeError: home() missing 1 required positional argument: 'name' |
| ''' |
【6】有参装饰器
- 之所以会抛出异常,是因为home(egon)调用的其实是wrapper(egon),而函数wrapper没有参数。
- wrapper函数接收的参数其实是给最原始的func用的
- 为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合
- 于是修正装饰器timer如下
| import time |
| |
| |
| def home(name): |
| time.sleep(5) |
| print('Welcome to the home page', name) |
| |
| |
| |
| def wrapper(func): |
| |
| def inner(*args, **kwargs): |
| start_time = time.time() |
| res = func(*args, **kwargs) |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
| |
| return inner |
| |
| |
| |
| home = wrapper(home) |
| |
| |
| home("Dream") |
【7】装饰器练习
| |
| |
| |
| |
| |
| |
| |
| user_data_dict = {'dream': 19, 'hope': 17} |
| |
| |
| |
| def outer(func): |
| def inner(): |
| '''这里写调用 func 函数之前的逻辑''' |
| res = func() |
| '''这里写调用 func 函数之后的逻辑''' |
| return res |
| |
| return inner |
| |
| |
| def watch(): |
| ... |
| |
| |
| def see(): |
| ... |
| user_dict = {'user': 17, 'dream': 99} |
| |
| def outer(func): |
| username = input("请输入用户名:") |
| |
| def inner(): |
| if user_dict[username] >= 18: |
| return func() |
| else: |
| return "尚未成年,该电影你还不可以看哟~" |
| return inner |
| |
| def watch(): |
| return "你已经是成年人了,可以来看了" |
| |
| watch = outer(watch) |
| print(watch()) |
【五】伪装,修复装饰器
| def outter1(func1): |
| print('加载了outter1') |
| |
| def wrapper1(*args, **kwargs): |
| print('执行了wrapper1') |
| res1 = func1(*args, **kwargs) |
| return res1 |
| |
| return wrapper1 |
| |
| |
| def index(): |
| print('from index') |
| |
| print(index) |
| |
| @outter |
| def index(): |
| print('from index') |
| |
| |
| print(index) |
【1】查看装饰器help
- 可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性
| import time |
| |
| |
| |
| def wrapper(func): |
| |
| def inner(*args, **kwargs): |
| start_time = time.time() |
| res = func(*args, **kwargs) |
| stop_time = time.time() |
| print('run time is %s' % (stop_time - start_time)) |
| |
| return inner |
| |
| |
| @wrapper |
| def home(name): |
| ''' |
| home page function |
| :param name: str |
| :return: None |
| ''' |
| time.sleep(5) |
| print('Welcome to the home page', name) |
| |
| |
| print(help(home)) |
| ''' |
| 打印结果: |
| |
| Help on function ###wrapper### in module __main__: |
| |
| wrapper(*args, **kwargs) |
| |
| None |
| ''' |
- 通过
help
函数来查看home函数文档注释,却打印了wrapper
的文档注释
wraps
是 Python 中 functools
模块提供的一个装饰器,主要用于解决装饰器可能引发的一个问题:装饰器会改变被装饰函数的一些元信息(metadata),比如函数的名称、文档字符串等。使用 wraps
装饰器可以保留原始函数的这些元信息。
具体来说,wraps
的作用有以下几点:
- 保留函数名称: 装饰器会替换原始函数,如果不使用
wraps
,那么被装饰后的函数的名称可能会变成装饰器函数的名称。使用 wraps
可以确保被装饰函数的名称不受影响。
- 保留文档字符串: 装饰器可能会导致被装饰函数失去原始的文档字符串。使用
wraps
可以确保被装饰函数继续具有正确的文档字符串。
- 保留模块信息: 除了名称和文档字符串外,
wraps
还有助于保留原始函数的模块信息,确保被装饰后的函数与原始函数在同一模块中。
示例代码:
| from functools import wraps |
| |
| def my_decorator(func): |
| @wraps(func) |
| def wrapper(*args, **kwargs): |
| print("Something is happening before the function is called.") |
| result = func(*args, **kwargs) |
| print("Something is happening after the function is called.") |
| return result |
| return wrapper |
| |
| @my_decorator |
| def say_hello(): |
| """A simple function that prints 'Hello!'""" |
| print("Hello!") |
| |
| |
| say_hello() |
| |
| |
| print(f"Function name: {say_hello.__name__}") |
| print(f"Function docstring: {say_hello.__doc__}") |
在这个例子中,如果不使用 wraps
,say_hello
函数的名称可能会变成 wrapper
,并且文档字符串也可能会丢失。使用了 @wraps(func)
,say_hello
保留了正确的名称和文档字符串。
| from functools import wraps |
| |
| |
| def my_decorator(func): |
| @wraps(func) |
| def wrapper(*args, **kwargs): |
| """Wrapper docstring.""" |
| print("Something is happening before the function is called.") |
| result = func(*args, **kwargs) |
| print("Something is happening after the function is called.") |
| return result |
| |
| return wrapper |
| |
| |
| @my_decorator |
| def example_function(): |
| """Original function docstring.""" |
| print("Inside the original function.") |
| |
| |
| |
| help(example_function) |
| |
| |
| |
| |
| |
| def my_decorator(func): |
| |
| def wrapper(*args, **kwargs): |
| """Wrapper docstring.""" |
| print("Something is happening before the function is called.") |
| result = func(*args, **kwargs) |
| print("Something is happening after the function is called.") |
| return result |
| |
| return wrapper |
| |
| |
| @my_decorator |
| def example_function(): |
| """Original function docstring.""" |
| print("Inside the original function.") |
| |
| |
| |
| help(example_function) |
| |
| |
| |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了