Python装饰器decorator实际应用与高级使用详解
作者:Tipriest_
在这篇文章中,我们来详细、系统地讲解一下Python的装饰器(Decorator)。我会从基本概念讲起,循序渐进,直到实际应用和高级用法。
1. 什么是装饰器?(核心思想)
装饰器本质上是一个Python函数,它可以让其他函数在不改变其源代码的情况下增加额外的功能。
核心思想:包装 (Wrapping)。
你可以把装饰器想象成一个包装礼物的过程:
- 你的函数 = 礼物本身
- 装饰器 = 包装纸、彩带和蝴蝶结
礼物(函数)的核心功能没有变,但经过包装(装饰器)后,它看起来更漂亮、更完整了(增加了新功能)。
在代码层面,装饰器是一个接收函数作为参数,并返回一个新函数的函数。
2. 为什么需要装饰器?(动机)
假设我们有很多函数,现在想给每个函数都加上一个“计算运行时间”的功能。
不好的方法:修改每个函数
import time
def func_A():
start_time = time.time()
print("函数A开始执行...")
time.sleep(1)
print("函数A执行完毕。")
end_time = time.time()
print(f"函数A耗时: {end_time - start_time:.2f}秒")
def func_B():
start_time = time.time()
print("函数B开始执行...")
time.sleep(2)
print("函数B执行完毕。")
end_time = time.time()
print(f"函数B耗时: {end_time - start_time:.2f}秒")
# ... 还有 func_C, func_D ...
问题:
- 代码冗余:计时逻辑在每个函数里都重复了一遍。
- 违反“开放封闭原则”:每次新增需求(比如再加个日志功能),都要去修改已有函数的代码。
装饰器就是为了解决这类问题而生的,它能将这些通用的、与核心业务无关的功能(如计时、日志、认证)抽离出来。
3. Python基础:理解装饰器的前提
要理解装饰器,必须先理解Python中 “函数是一等公民” (Functions are First-Class Objects) 的概念。这意味着函数可以:
被赋值给一个变量
def say_hello():
print("Hello!")
greet = say_hello # 将函数say_hello赋值给变量greet
greet() # 输出: Hello!
作为参数传递给另一个函数
def do_something(func):
print("准备做某事...")
func() # 调用传入的函数
print("做完了。")
def work():
print("正在努力工作...")
do_something(work)
# 输出:
# 准备做某事...
# 正在努力工作...
# 做完了。
在函数内部定义,并作为返回值返回
def get_greeter():
def greet():
print("你好,世界!")
return greet # 返回内部定义的greet函数
greeter_func = get_greeter()
greeter_func() # 输出: 你好,世界!
装饰器正是利用了这几点特性,尤其是第2点和第3点。
4. 一步步构建你的第一个装饰器
最简单的装饰器(手动方式)
我们来写一个简单的装饰器,它在函数执行前后打印信息。
# 1. 定义一个装饰器函数
def my_decorator(func):
# 2. 定义一个内部包装函数,它将“包装”原始函数
def wrapper():
print("在被装饰的函数执行之前...")
func() # 3. 调用原始函数
print("在被装饰的函数执行之后...")
# 4. 返回这个包装好的函数
return wrapper
# 定义一个需要被装饰的函数
def say_whee():
print("Whee!")
# 手动使用装饰器
say_whee = my_decorator(say_whee)
# 现在调用say_whee,实际上是在调用wrapper函数
say_whee()
输出:
在被装饰的函数执行之前... Whee! 在被装饰的函数执行之后...
say_whee = my_decorator(say_whee) 这行代码是理解装饰器的关键。
它用 my_decorator 返回的 wrapper 函数覆盖了原来的 say_whee 函数。
使用@语法糖
Python提供了一个更优雅、更Pythonic的方式来使用装饰器,那就是 @ 语法糖。
def my_decorator(func):
def wrapper():
print("在被装饰的函数执行之前...")
func()
print("在被装饰的函数执行之后...")
return wrapper
@my_decorator # 这行等价于 say_whee = my_decorator(say_whee)
def say_whee():
print("Whee!")
say_whee()
输出和上面完全一样,但代码是不是简洁多了?
5. 处理带参数的函数
如果我们的原始函数带参数怎么办?比如 greet(name)。上面的 wrapper 函数不接受任何参数,会报错。
我们需要让 wrapper 函数能接受任意参数,并把它们传递给原始函数。这里就要用到 *args 和 **kwargs。
def decorator_with_args(func):
# wrapper现在可以接受任意数量的位置参数和关键字参数
def wrapper(*args, **kwargs):
print("装饰器:函数开始执行")
result = func(*args, **kwargs) # 将参数传递给原始函数,并保存返回值
print("装饰器:函数执行完毕")
return result # 返回原始函数的执行结果
return wrapper
@decorator_with_args
def greet(name, message="你好"):
print(f"{message}, {name}!")
return f"问候了 {name}"
returned_value = greet("Alice", message="早上好")
print(f"函数返回值: {returned_value}")
输出:
装饰器:函数开始执行 早上好, Alice! 装饰器:函数执行完毕 函数返回值: 问候了 Alice
6.functools.wraps的重要性
使用装饰器有一个小问题:它会丢失原始函数的一些元信息(metadata),比如函数名 (__name__)、文档字符串 (__doc__)等。
@decorator_with_args
def greet(name):
"""这是一个打招呼的函数"""
print(f"你好, {name}!")
print(greet.__name__) # 输出: wrapper (而不是 greet)
print(greet.__doc__) # 输出: None (而不是 "这是一个打招呼的函数")
这对于调试和自省工具来说非常不便。为了解决这个问题,Python的 functools 模块提供了一个专门的装饰器:@wraps。
import functools
def decorator_for_wraps(func):
@functools.wraps(func) # 关键!将func的元信息拷贝到wrapper上
def wrapper(*args, **kwargs):
# ... 装饰器逻辑 ...
print("装饰器逻辑...")
return func(*args, **kwargs)
return wrapper
@decorator_for_wraps
def greet(name):
"""这是一个打招呼的函数"""
print(f"你好, {name}!")
print(greet.__name__) # 输出: greet
print(greet.__doc__) # 输出: 这是一个打招呼的函数
最佳实践: 编写任何装饰器时,都应该在你的 wrapper 函数上使用 @functools.wraps。
7. 带参数的装饰器(进阶)
如果我们想让装饰器本身也接收参数呢?比如 @repeat(num=3),让函数重复执行3次。
这就需要再加一层函数嵌套。
结构:
- 最外层函数 (
repeat):接收装饰器的参数(如num=3),并返回一个真正的装饰器。 - 中间层函数 (
decorator_repeat):这就是我们之前写的标准装饰器,它接收一个函数作为参数。 - 最内层函数 (
wrapper):执行增强后的逻辑。
import functools
def repeat(num=2): # 1. 最外层函数,接收装饰器参数
def decorator_repeat(func): # 2. 中间层,接收被装饰的函数
@functools.wraps(func)
def wrapper(*args, **kwargs): # 3. 最内层,执行逻辑
print(f"函数将重复执行 {num} 次。")
for _ in range(num):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num=4)
def say_hello(name):
print(f"Hello, {name}!")
say_hello("World")
执行过程:
@repeat(num=4)首先被调用,它返回decorator_repeat函数。- Python接着执行
@decorator_repeat,这等价于say_hello = decorator_repeat(say_hello)。 - 最终
say_hello变量指向了wrapper函数。
8. 类装饰器(进阶)
除了用函数,我们也可以用类来创建装饰器。这在需要维护状态时特别有用。一个类要成为装饰器,需要实现 __init__ 和 __call__ 方法。
class Counter:
def __init__(self, func):
functools.update_wrapper(self, func) # 类似 @wraps
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"'{self.func.__name__}' 已被调用 {self.num_calls} 次。")
return self.func(*args, **kwargs)
@Counter
def some_function():
print("执行 some_function")
some_function()
some_function()
some_function()
输出:
'some_function' 已被调用 1 次。 执行 some_function 'some_function' 已被调用 2 次。 执行 some_function 'some_function' 已被调用 3 次。 执行 some_function
Counter 类的实例 some_function 能够记住自己被调用的次数。
9. 装饰器的实际应用场景(重点)
日志记录 (Logging)
import functools
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def log_function_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Calling function '{func.__name__}' with args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
logging.info(f"Function '{func.__name__}' returned {result}")
return result
return wrapper
@log_function_call
def add(a, b):
return a + b
add(5, 3)
性能计时 (Timing)
import time
import functools
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
print(f"Function '{func.__name__}' took {run_time:.4f} seconds to complete.")
return result
return wrapper
@timer
def process_data():
time.sleep(1)
print("数据处理完成!")
process_data()
用户认证 (Authentication)
在Web框架(如Flask, Django)中非常常见。
# 这是一个简化的概念性例子
import functools
def login_required(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 伪代码:假设有一个函数 check_user_logged_in()
if check_user_logged_in():
return func(*args, **kwargs)
else:
raise PermissionError("用户未登录,禁止访问!")
return wrapper
# 模拟一个全局登录状态
_user_is_logged_in = False
def check_user_logged_in(): return _user_is_logged_in
@login_required
def view_profile():
print("这是用户的个人资料页面。")
# 尝试调用
try:
view_profile()
except PermissionError as e:
print(e) # 输出: 用户未登录,禁止访问!
# 模拟用户登录
_user_is_logged_in = True
print("\n用户登录后:")
view_profile() # 输出: 这是用户的个人资料页面。
缓存 (Caching/Memoization)
对于计算成本高的函数,可以将结果缓存起来。Python 3.9+ 自带了 functools.cache。我们也可以自己实现一个简单的版本。
import functools
import time
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
# 使用Python自带的会更高效
# from functools import lru_cache, cache
@memoize # 或者 @functools.cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
start = time.time()
print(fibonacci(35))
end = time.time()
print(f"第一次计算耗时: {end - start:.4f}s")
start = time.time()
print(fibonacci(35))
end = time.time()
print(f"第二次(从缓存)计算耗时: {end - start:.4f}s")
输入验证 (Validation)
在函数执行前检查其参数是否符合要求。
import functools
def validate_types(*type_args):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i, (arg, expected_type) in enumerate(zip(args, type_args)):
if not isinstance(arg, expected_type):
raise TypeError(f"Argument {i+1} must be of type {expected_type.__name__}, not {type(arg).__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(int, int)
def multiply(a, b):
return a * b
print(multiply(5, 10)) # 正常工作
try:
multiply(5, "10") # 会抛出 TypeError
except TypeError as e:
print(e)
10. 装饰器栈(多个装饰器)
一个函数可以被多个装饰器修饰。执行顺序是从下到上(最靠近函数的先被应用),然后执行时是从上到下。
@timer
@log_function_call
def complex_calculation(x, y):
time.sleep(0.5)
return x * y + 2
这等价于:
complex_calculation = timer(log_function_call(complex_calculation))
执行 complex_calculation(3, 4) 时:
timer的wrapper开始执行,记录开始时间。timer调用log_function_call的wrapper。log_function_call的wrapper开始执行,打印日志 “Calling function…”。log_function_call调用原始的complex_calculation函数。complex_calculation执行,返回结果14。log_function_call的wrapper拿到结果,打印日志 “…returned 14”。timer的wrapper拿到结果,记录结束时间,并打印耗时。
11. 总结
- 核心:装饰器是一个接收函数并返回新函数的函数,用于在不修改原函数代码的情况下增加功能。
- 基础:依赖于Python中函数是“一等公民”的特性。
- 语法糖:
@decorator是my_func = decorator(my_func)的简写。 - 通用性:使用
*args, **kwargs来处理任意参数的函数。 - 最佳实践:始终使用
@functools.wraps来保留原函数的元信息。 - 灵活性:装饰器可以带参数,也可以用类来实现。
- 强大应用:日志、计时、认证、缓存、验证等都是装饰器的经典用例,极大地提高了代码的复用性和可维护性。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
