文章目录
0. 引言
Python装饰器(Decorator) 不仅可以让你的代码更加简洁、可读,还能有效地实现功能的复用和扩展。本文将带你深入了解Python装饰器的概念、原理及其应用。
1. 什么是装饰器?
装饰器是一种高阶函数,它接受一个函数作为参数,并返回一个新的函数。通过装饰器,我们可以在不修改原函数代码的前提下,动态地为其添加额外的功能。
简单来说,装饰器就是函数的包装器,它可以在函数执行前后添加一些操作。
2. 装饰器的基本语法
在Python中,装饰器通常使用 @
符号来应用于函数或类。下面是一个简单的装饰器示例:
python">def my_decorator(func):def wrapper():print("函数执行前的操作")func()print("函数执行后的操作")return wrapper@my_decorator
def say_hello():print("Hello!")say_hello()
输出:
函数执行前的操作
Hello!
函数执行后的操作
在这个例子中:
my_decorator
是一个装饰器,它接受一个函数func
作为参数。wrapper
是一个内部函数,它在调用func
前后添加了额外的打印操作。@my_decorator
将say_hello
函数传递给装饰器,并将返回的wrapper
函数重新赋值给say_hello
。
因此,当我们调用 say_hello()
时,实际执行的是 wrapper()
函数。
3. 装饰器的工作原理
装饰器的核心在于闭包和高阶函数。让我们通过一个更详细的示例来理解装饰器的工作机制。
python">def decorator(func):print("装饰器被调用")def wrapper(*args, **kwargs):print("在函数执行前")result = func(*args, **kwargs)print("在函数执行后")return resultreturn wrapper@decorator
def add(a, b):print(f"执行加法: {a} + {b}")return a + bresult = add(3, 5)
print(f"结果是: {result}")
输出:
装饰器被调用
在函数执行前
执行加法: 3 + 5
在函数执行后
结果是: 8
工作流程图示:
+------------------+
| @decorator |
| 装饰器被调用 |
| add 函数被传入 |
+---------+--------+|v
+---------+--------+
| 返回 wrapper 函数 |
+---------+--------+|v
+---------+--------+
| 调用 add(3, 5) | ---> 实际上调用的是 wrapper(3, 5)
+---------+--------+|v
+---------+--------+
| 打印 "在函数执行前"|
| 调用原始 add |
| 打印 "执行加法:3 +5"|
| 打印 "在函数执行后"|
+---------+--------+|v
+---------+--------+
| 返回结果 8 |
+------------------+
解释:
- 当Python解释器遇到
@decorator
时,它会先调用decorator(add)
。 decorator
函数在执行时首先打印“装饰器被调用”。decorator
返回了wrapper
函数,因此add
函数被替换为wrapper
。- 当调用
add(3, 5)
时,实际上调用的是wrapper(3, 5)
,它在执行func(3, 5)
(即原始的add
函数)前后添加了打印操作。
4. 常见装饰器应用场景
装饰器在实际开发中有着广泛的应用,以下是几个常见的使用场景:
4.1. 日志记录
记录函数的调用信息、参数、返回值等,有助于调试和监控。
python">def log_decorator(func):def wrapper(*args, **kwargs):print(f"调用函数 {func.__name__},参数: {args}, {kwargs}")result = func(*args, **kwargs)print(f"函数 {func.__name__} 返回: {result}")return resultreturn wrapper@log_decorator
def multiply(a, b):return a * bmultiply(4, 5)
输出:
调用函数 multiply,参数: (4, 5), {}
函数 multiply 返回: 20
4.2. 权限校验
在函数执行前进行权限检查,确保用户有权限执行该操作。
python">def requires_permission(permission):def decorator(func):def wrapper(*args, **kwargs):if not user_has_permission(permission):raise PermissionError("没有权限执行此操作")return func(*args, **kwargs)return wrapperreturn decorator@requires_permission('admin')
def delete_user(user_id):print(f"删除用户 {user_id}")
4.3. 缓存
缓存函数的计算结果,避免重复计算,提高性能。
python">def cache_decorator(func):cache = {}def wrapper(*args):if args in cache:print("使用缓存")return cache[args]result = func(*args)cache[args] = resultreturn resultreturn wrapper@cache_decorator
def fibonacci(n):if n <= 1:return nreturn fibonacci(n-1) + fibonacci(n-2)print(fibonacci(5))
5. 多重装饰器的执行顺序
当一个函数被多个装饰器装饰时,装饰器的执行顺序可能会让人感到困惑。下面通过一个示例来说明多重装饰器的执行顺序。
python">def decorator_a(func):print("装饰器 A 被调用")def wrapper(*args, **kwargs):print("装饰器 A 在函数执行前")result = func(*args, **kwargs)print("装饰器 A 在函数执行后")return resultreturn wrapperdef decorator_b(func):print("装饰器 B 被调用")def wrapper(*args, **kwargs):print("装饰器 B 在函数执行前")result = func(*args, **kwargs)print("装饰器 B 在函数执行后")return resultreturn wrapper@decorator_a
@decorator_b
def greet(name):print(f"Hello, {name}!")greet("Alice")
输出:
装饰器 A 被调用
装饰器 B 被调用
装饰器 A 在函数执行前
装饰器 B 在函数执行前
Hello, Alice!
装饰器 B 在函数执行后
装饰器 A 在函数执行后
执行顺序图示:
装饰器应用阶段:
+-----------------+ +-----------------+
| decorator_a | | decorator_b |
| 调用 decorator_a | | 调用 decorator_b |
+--------+--------+ +--------+--------+| |v v
+--------+--------+ +--------+--------+
| 返回 wrapper_a | | 返回 wrapper_b |
+--------+--------+ +--------+--------+| |+----------> greet <-------+指向 wrapper_a函数调用阶段:
+-----------------+
| 调用 greet("Alice") |
+--------+--------+|v
+--------+--------+
| wrapper_a |
| 打印 "装饰器 A 在函数执行前" |
| 调用 wrapper_b |
+--------+--------+|v
+--------+--------+
| wrapper_b |
| 打印 "装饰器 B 在函数执行前" |
| 调用 greet ("Hello, Alice!") |
| 打印 "装饰器 B 在函数执行后" |
+--------+--------+|v
+--------+--------+
| wrapper_a |
| 打印 "装饰器 A 在函数执行后" |
+-----------------+
解释:
-
装饰器的应用顺序是自下而上:
- 首先,
greet
函数被decorator_b
装饰,生成wrapper_b
。 - 然后,
wrapper_b
被decorator_a
装饰,生成wrapper_a
。 - 最终,
greet
指向wrapper_a
。
- 首先,
-
函数调用的执行顺序是自上而下:
6. 装饰器的高级用法
6.1. 带参数的装饰器
有时候,装饰器本身需要接受参数,这时需要使用三层嵌套函数。
python">def repeat(num_times):def decorator(func):def wrapper(*args, **kwargs):for _ in range(num_times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@repeat(num_times=3)
def say(message):print(message)say("Hello!")
输出:
Hello!
Hello!
Hello!
6.2. 使用 functools.wraps
在装饰器中,使用 functools.wraps
可以保留原函数的元数据,如函数名、文档字符串等。
python">import functoolsdef my_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("调用前")return func(*args, **kwargs)return wrapper@my_decorator
def example():"""这是一个示例函数"""print("示例函数执行")print(example.__name__) # 输出: example
print(example.__doc__) # 输出: 这是一个示例函数
6.3. 类装饰器
装饰器不仅可以用于函数,也可以用于类。
python">def class_decorator(cls):class WrappedClass:def __init__(self, *args, **kwargs):self.wrapped_instance = cls(*args, **kwargs)def __getattr__(self, attr):return getattr(self.wrapped_instance, attr)def new_method(self):print("这是新添加的方法")return WrappedClass@class_decorator
class MyClass:def method(self):print("原始方法")obj = MyClass()
obj.method()
obj.new_method()
输出:
原始方法
这是新添加的方法
7. 图示说明
为了更直观地理解装饰器的工作原理及其执行顺序,下面通过几张示意图来辅助说明。
7.1. 单一装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+
| Original Func | (被装饰的函数)
+--------+--------+|v
+--------+--------+
| Decorator | (装饰器函数)
| 返回 Wrapper |
+--------+--------+|v
+--------+--------+
| Wrapper Func | (包装后的函数)
+--------+--------+函数调用阶段:
+-----------------+
| 调用 Wrapper |
+--------+--------+|v
+--------+--------+
| 执行装饰器前操作 |
+--------+--------+|v
+--------+--------+
| 执行原始函数 |
+--------+--------+|v
+--------+--------+
| 执行装饰器后操作 |
+-----------------+
解释:
2. 多重装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+ +-----------------+
| Original Func | | Decorator B |
+--------+--------+ +--------+--------+| |v v
+--------+--------+ +--------+--------+
| Decorator A | | 返回 Wrapper B |
| 返回 Wrapper A | +-----------------+
+--------+--------+|v
+--------+--------+
| Wrapper A |
+-----------------+函数调用阶段:
+-----------------+
| 调用 Wrapper A |
+--------+--------+|v
+--------+--------+
| 执行 Decorator A 前操作 |
+--------+--------+|v
+--------+--------+
| 调用 Wrapper B |
+--------+--------+|v
+--------+--------+
| 执行 Decorator B 前操作 |
+--------+--------+|v
+--------+--------+
| 执行原始函数 |
+--------+--------+|v
+--------+--------+
| 执行 Decorator B 后操作 |
+--------+--------+|v
+--------+--------+
| 执行 Decorator A 后操作 |
+-----------------+
解释:
-
装饰器应用阶段:
-
函数调用阶段:
3. 带参数装饰器的执行流程
示意图:
装饰器应用阶段:
+-----------------+
| repeat(num_times=3) |
+--------+--------+|v
+--------+--------+
| Decorator |
| 返回 Wrapper |
+--------+--------+|v
+--------+--------+
| 原始函数 |
+--------+--------+函数调用阶段:
+-----------------+
| 调用 Wrapper |
+--------+--------+|v
+--------+--------+
| 重复调用原始函数 |
| 3 次 |
+-----------------+
解释:
- 装饰器应用阶段:带参数的装饰器
repeat
接受参数num_times=3
,返回装饰器函数decorator
,然后decorator
返回wrapper
函数。 - 函数调用阶段:调用
wrapper
时,根据num_times
的值,重复调用原始函数 3 次。
总结
装饰器通过函数包装器的方式,允许开发者在不修改原函数代码的前提下,为其添加额外的功能。