Python函数:装饰器
一、装饰器的定义(什么是装饰器)
1)装饰器(Decorator)是一种特殊的函数,它的主要作用是在不修改被装饰函数的源代码和调用方式的前提下,为函数增加额外功能
2)本质:高阶函数 + 闭包。
形式表达
给定一个函数 func,装饰器 decorator 的本质是:decorator(func)→wrapper
其中,wrapper 是对 func 的包装,通常在调用时会执行额外操作,然后调用原函数 func。
逐步拆解
1)装饰器是一个函数,它接收另一个函数作为参数,比如 func
。
2)装饰器内部定义了一个新的函数 wrapper
,这个函数会在调用时:先执行一些额外的操作(比如打印日志、权限检查等)、再调用传入的原函数 func
3)装饰器返回这个新的函数 wrapper
,代替了原来的函数。
举例说明
def decorator(func):def wrapper():print("调用函数前的操作")func() # 调用原函数print("调用函数后的操作")return wrapper # 返回包装后的函数def say_hello():print("Hello!")# 使用装饰器包装函数
say_hello = decorator(say_hello)say_hello()
"""
结果:
调用函数前的操作
Hello!
调用函数后的操作
"""
解释
1)decorator(say_hello) 返回了一个新的函数 wrapper,这个函数包裹了原来的 say_hello。
2)当你调用 say_hello() 时,实际上执行的是 wrapper(),它先打印“调用函数前的操作”,然后调用原函数 say_hello(),最后打印“调用函数后的操作”。
二、装饰器有什么用
- 增强函数功能
在函数执行前后添加额外操作,比如日志记录、权限校验、性能统计等。 - 代码复用与解耦
把通用的功能抽象成装饰器,多个函数复用,避免重复代码。 - 函数行为的动态修改
在运行时动态地改变函数行为,而无需修改函数本身代码。 - 简化代码结构
通过装饰器,代码结构更清晰,主业务逻辑和辅助功能分离。
为什么要使用装饰器
- 避免代码重复
例如,多个函数都需要日志打印或权限检查,用装饰器封装一次,复用多处。 - 统一管理横切关注点
许多功能(如缓存、权限、事务)是“横切关注点”,装饰器能集中管理,代码更整洁。 - 提高代码可读性和维护性
业务逻辑与辅助功能分开,代码更易于理解和维护。 - 符合开放封闭原则
不修改已有函数代码,扩展功能,符合软件设计原则。
方面 | 说明 |
装饰器作用 | 给函数动态添加功能,增强代码复用性 |
主要用途 | 日志、权限、缓存、性能统计、事务管理 |
使用理由 | 避免重复代码,分离关注点,提高维护性 |
不使用影响 | 重复代码多,重复,难维护 |
推荐使用场景 | 多函数共享的辅助功能 |
三、装饰器如何使用
3.1 装饰器的基本语法
装饰器本质是一个函数,接受一个函数作为参数,返回一个新的函数。
def decorator(func):def wrapper():# 在调用原函数前可以做一些操作print("开始执行函数")func()# 在调用原函数后可以做一些操作print("函数执行完毕")return wrapper@decorator # 语法糖,等同于 foo = decorator(foo)
def foo():print("这是被装饰的函数")foo()
"""
输出:
复制
开始执行函数
这是被装饰的函数
函数执行完毕
"""
装饰器的工作流程
@decorator
把被装饰函数foo
传给decorator
函数decorator
返回一个新函数wrapper
foo
被替换成了wrapper
- 调用
foo()
实际调用的是wrapper()
,在其中调用原始foo
3.2 带参数的被装饰函数
如果被装饰函数有参数,装饰器的内部函数也要支持接收参数:
def decorator(func):def wrapper(*args, **kwargs):print("开始执行函数")result = func(*args, **kwargs)print("函数执行完毕")return resultreturn wrapper@decorator
def add(a, b):return a + bprint(add(3, 4)) # 输出 7
3.3 带参数的装饰器
有时装饰器本身也需要参数,这时需要三层嵌套:
def repeat(times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(times):func(*args, **kwargs)return wrapperreturn decorator@repeat(times=3)
def greet(name):print(f"Hello, {name}!")greet("Alice")
"""
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
"""
3.4 链式装饰器(多个装饰器叠加)
def deco1(func):def wrapper():print("deco1 before")func()print("deco1 after")return wrapperdef deco2(func):def wrapper():print("deco2 before")func()print("deco2 after")return wrapper@deco1
@deco2
def foo():print("foo")foo()
"""
输出:
deco1 before
deco2 before
foo
deco2 after
deco1 after
"""
解释:
(1)装饰器应用顺序
Python中多个装饰器叠加时,装饰器的应用顺序是从下往上执行的。
- foo 先被 deco2 装饰,变成 deco2(foo),返回一个新的函数(deco2 的 wrapper)。
- 然后这个新函数又被 deco1 装饰,变成 deco1(deco2(foo)),返回最终包装后的函数(deco1 的 wrapper)。
(2)调用过程
当执行 foo() 时,实际上调用的是最外层装饰器 deco1 返回的 wrapper 函数。
(3)执行细节
- deco1 的 wrapper 首先打印 "deco1 before"。
- 然后调用它内部的 func(),此时 func 是 deco2(foo) 返回的 wrapper 函数。
- 进入 deco2 的 wrapper,打印 "deco2 before"。
- 调用原始的 foo() 函数,打印 "foo"。
- deco2 的 wrapper 打印 "deco2 after",执行结束返回。
- 回到 deco1 的 wrapper,打印 "deco1 after",执行结束返回。
(4)总结
- 装饰器的嵌套调用使得输出顺序呈现“先外后内,后内先出”的特点。
- 也就是说,装饰器的“before”部分按装饰器叠加顺序从外到内依次执行,“after”部分则按相反顺序执行。
四、易错点分析
4.1 装饰器不支持带参数的函数(忘写*args, **kwargs
)
错误示例:
def decorator(func):def wrapper():print("开始执行")func() # 如果被装饰函数有参数,这里会报错return wrapper@decorator
def foo(name):print(f"Hello, {name}")foo("Alice") # TypeError: wrapper() takes 0 positional arguments but 1 was given
正确写法:
def decorator(func):def wrapper(*args, **kwargs):print("开始执行")return func(*args, **kwargs)return wrapper
4.2 装饰器顺序错误导致逻辑混乱(链式装饰器)
多个装饰器叠加时,装饰器的应用顺序和调用顺序容易混淆。
- 装饰器从下往上应用。
- 调用时从最外层装饰器开始。
4.3 装饰器中忘记返回函数结果
包装函数如果没有返回被装饰函数的返回值,会导致调用者拿不到结果。
def decorator(func):def wrapper(*args, **kwargs):func(*args, **kwargs) # 忘记returnreturn wrapper@decorator
def add(a, b):return a + bprint(add(1, 2)) # 输出 None,而不是 3
正确写法:
def decorator(func):def wrapper(*args, **kwargs):return func(*args, **kwargs)return wrapper
4.4 带参数装饰器忘记多层嵌套
带参数的装饰器必须三层函数嵌套,否则参数无法传递