深入理解Python装饰器:从入门到精通

📅 发布时间:2026/7/2 20:18:36 👁️ 浏览次数:
深入理解Python装饰器:从入门到精通
1. 引言在Python编程中装饰器Decorator是一种非常强大且优雅的设计模式它允许我们在不修改原有函数代码的情况下为函数添加新的功能。装饰器本质上是一个可调用对象函数或类它接收一个函数作为参数并返回一个新的函数。这种模式遵循了“开放封闭原则”——对扩展开放对修改封闭。无论是Web框架中的路由注册、Django中的权限控制、还是Flask中的请求预处理装饰器都扮演着核心角色。掌握装饰器不仅能让你的代码更加简洁、可复用更能帮助你深入理解Python的函数式编程特性。本文将带你从最基础的闭包概念开始逐步深入装饰器的底层原理再通过大量实战案例让你彻底掌握装饰器的编写与应用。2. 装饰器的前置知识闭包要理解装饰器必须先理解Python中的闭包Closure。闭包是指在一个外部函数中定义的内部函数并且这个内部函数引用了外部函数的变量同时外部函数将内部函数作为返回值返回。这样即使外部函数执行完毕内部函数依然可以记住它所引用的外部变量。2.1 闭包的基本形式def outer_func(msg): # 外部函数的局部变量 message msg def inner_func(): # 内部函数引用了外部函数的变量 print(message) return inner_func # 创建闭包 my_closure outer_func(Hello, Closure!) # 调用内部函数 my_closure() # 输出: Hello, Closure!在这个例子中inner_func就是一个闭包它记住了外部函数outer_func中的message变量即使outer_func已经执行完毕。2.2 闭包的作用域特点我们可以通过__closure__属性查看闭包中捕获的变量def outer(x): y 10 def inner(): print(x, y) return inner closure_func outer(5) print(closure_func.__closure__) # (cell at 0x...: int object at 0x..., cell at 0x...: int object at 0x...) print(closure_func.__closure__[0].cell_contents) # 5 print(closure_func.__closure__[1].cell_contents) # 10闭包的特性是实现装饰器的关键因为装饰器本质上就是在内部函数中包裹被装饰函数并访问外部函数传入的函数参数。2.3 闭包与普通嵌套函数的区别- 普通嵌套函数内部函数只在外部函数内部被调用外部函数返回后无法再使用内部函数。- 闭包外部函数返回内部函数的引用内部函数可以在外部函数结束后继续使用外部函数的变量。3. 装饰器的工作原理从语法层面看装饰器使用decorator语法糖。但它的本质其实是高阶函数——接收函数作为参数并返回新函数的函数。下面这个例子揭示了装饰器的原始调用方式def simple_decorator(func): def wrapper(): print(在函数执行前做点什么) func() print(在函数执行后做点什么) return wrapper def say_hello(): print(Hello!) # 不使用语法 say_hello simple_decorator(say_hello) say_hello()输出在函数执行前做点什么Hello!在函数执行后做点什么而使用语法糖等价于上面的手动赋值过程simple_decorator def say_hello(): print(Hello!) say_hello() # 输出结果完全相同所以装饰器并没有任何“魔法”它就是一种函数替换将原函数替换成装饰器返回的新函数wrapper。4. 第一个装饰器函数装饰器4.1 装饰无参数的函数最简单的装饰器需要处理被装饰函数没有参数的情况def my_decorator(func): def wrapper(): print(开始执行函数...) result func() print(函数执行结束。) return result return wrapper my_decorator def greet(): print(欢迎来到Python世界) greet() 输出 开始执行函数... 欢迎来到Python世界 函数执行结束。 4.2 装饰有参数的函数如果被装饰函数带有参数wrapper需要接受任意参数并传递给原函数。使用*args和**kwargs是最佳实践def log_decorator(func): def wrapper(*args, **kwargs): print(f调用函数: {func.__name__}) print(f位置参数: {args}) print(f关键字参数: {kwargs}) result func(*args, **kwargs) print(f返回值: {result}) return result return wrapper log_decorator def add(a, b, c0): return a b c add(3, 5, c2) 输出 调用函数: add 位置参数: (3, 5) 关键字参数: {c: 2} 返回值: 10 4.3 装饰带有返回值的函数注意wrapper必须返回原函数的返回值否则装饰后的函数会变成Nonedef preserve_return(func): def wrapper(*args, **kwargs): # 做一些额外操作 result func(*args, **kwargs) # 保存返回值 # 再做其他操作 return result # 必须返回 return wrapper preserve_return def multiply(x, y): return x * y print(multiply(4, 5)) # 20如果不写return result则会输出None造成意料之外的结果。5. 带参数的装饰器有时候我们需要给装饰器传递参数比如指定日志级别、重试次数、缓存过期时间等。这时需要三层嵌套函数第一层接收装饰器参数第二层接收被装饰函数第三层接收被装饰函数的参数。5.1 基本结构def repeat(times): 重复执行被装饰函数times次 def decorator(func): def wrapper(*args, **kwargs): results [] for i in range(times): print(f第{i1}次执行) result func(*args, **kwargs) results.append(result) return results return wrapper return decorator repeat(times3) def say_hello(name): return fHello, {name}! print(say_hello(Alice)) 输出 第1次执行 第2次执行 第3次执行 [Hello, Alice!, Hello, Alice!, Hello, Alice!] 5.2 带默认参数的装饰器可以设计装饰器让它既支持decorator也支持decorator(arg)两种用法类似lru_cache的设计。技巧是判断第一个参数是否为可调用对象from functools import wraps def retry(max_attempts3): if callable(max_attempts): # 如果直接retrymax_attempts就是被装饰函数 func max_attempts max_attempts 3 return _retry_decorator(func, max_attempts) else: # 如果retry(5)返回真正的装饰器 def decorator(func): return _retry_decorator(func, max_attempts) return decorator def _retry_decorator(func, max_attempts): wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except Exception as e: print(f第{attempt}次尝试失败: {e}) if attempt max_attempts: raise return None return wrapper # 两种使用方式 retry def unstable_func1(): import random if random.random() 0.7: return 成功 raise ValueError(随机失败) retry(5) def unstable_func2(): # 同上 pass这种高级用法体现了装饰器的灵活性。6. 类装饰器除了使用函数来实现装饰器我们也可以使用类来实现。类装饰器通常利用__init__和__call__方法。类装饰器的优势在于可以保存状态更适合需要维护配置信息的场景。6.1 类作为装饰器class CountCalls: def __init__(self, func): self.func func self.count 0 def __call__(self, *args, **kwargs): self.count 1 print(f函数 {self.func.__name__} 已被调用 {self.count} 次) return self.func(*args, **kwargs) CountCalls def say_hello(): print(Hello!) say_hello() # 第1次 say_hello() # 第2次 say_hello() # 第3次 输出 函数 say_hello 已被调用 1 次 Hello! 函数 say_hello 已被调用 2 次 Hello! 函数 say_hello 已被调用 3 次 Hello! 6.2 带参数的类装饰器如果需要给类装饰器传递参数只需在__init__中接收参数然后在__call__中接收函数class RepeatWithDelay: def __init__(self, times, delay0): self.times times self.delay delay def __call__(self, func): def wrapper(*args, **kwargs): import time results [] for i in range(self.times): results.append(func(*args, **kwargs)) if i self.times - 1: time.sleep(self.delay) return results return wrapper RepeatWithDelay(times3, delay1) def get_time(): import time return time.time() print(get_time()) # 每隔1秒打印一次时间戳6.3 类装饰器与函数装饰器的选择- 函数装饰器简单、轻量适合无状态或状态简单的场景。- 类装饰器适合需要维护复杂状态、提供多个方法如reset计数的场景代码组织更清晰。7. 多个装饰器的叠加与顺序一个函数可以同时应用多个装饰器。执行顺序是从下往上即离函数最近的装饰器先执行但包装过程是从内到外。看下面的例子def decorator_a(func): def wrapper(*args, **kwargs): print(A before) result func(*args, **kwargs) print(A after) return result return wrapper def decorator_b(func): def wrapper(*args, **kwargs): print(B before) result func(*args, **kwargs) print(B after) return result return wrapper decorator_a decorator_b def hello(): print(Hello World) hello() 输出 A before B before Hello World B after A after 为什么因为语法糖等价于hello decorator_a(decorator_b(hello))先执行decorator_b(hello)得到wrapper_b再执行decorator_a(wrapper_b)得到wrapper_a。调用时先进入wrapper_a打印A before然后调用内部的wrapper_b打印B before再调用原始hello打印Hello World然后依次退出。所以装饰器应用顺序是从下往上执行顺序是从上往下进入从下往上退出。8. functools.wraps的重要作用装饰器有一个“副作用”它会覆盖原函数的元信息如__name__、__doc__、__module__等。因为最终返回的是wrapper函数原来的函数名等信息丢失了。这对于调试、序列化、文档生成都不友好。def bad_decorator(func): def wrapper(*args, **kwargs): 这是wrapper的文档 return func(*args, **kwargs) return wrapper bad_decorator def add(a, b): 计算两个数的和 return a b print(add.__name__) # wrapper print(add.__doc__) # 这是wrapper的文档为了解决这个问题Python的functools模块提供了wraps装饰器它可以将被装饰函数的元信息拷贝到wrapper函数上。from functools import wraps def good_decorator(func): wraps(func) def wrapper(*args, **kwargs): 这是wrapper的文档但会被覆盖 return func(*args, **kwargs) return wrapper good_decorator def add(a, b): 计算两个数的和 return a b print(add.__name__) # add print(add.__doc__) # 计算两个数的和强烈建议在编写任何装饰器时务必在wrapper上使用wraps(func)。这是Python最佳实践。9. 实战应用场景下面通过几个真实场景展示装饰器的威力。9.1 函数执行时间统计import time from functools import wraps def timer(func): wraps(func) def wrapper(*args, **kwargs): start time.perf_counter() result func(*args, **kwargs) end time.perf_counter() elapsed (end - start) * 1000 print(f{func.__name__} 执行耗时: {elapsed:.4f} 毫秒) return result return wrapper timer def slow_function(): time.sleep(0.5) return 完成 slow_function() # 输出: slow_function 执行耗时: 500.1234 毫秒可以扩展为记录日志到文件或者只统计超过阈值的调用。9.2 权限校验装饰器模拟Flask-Loginfrom functools import wraps from flask import session, abort def login_required(func): wraps(func) def wrapper(*args, **kwargs): if user_id not in session: abort(401) # 未授权 return func(*args, **kwargs) return wrapper # 带角色校验的增强版 def role_required(allowed_roles): def decorator(func): wraps(func) def wrapper(*args, **kwargs): user_role session.get(role) if user_role not in allowed_roles: abort(403) # 禁止访问 return func(*args, **kwargs) return wrapper return decorator # 使用示例 app.route(/admin) login_required role_required([admin]) def admin_panel(): return 欢迎管理员9.3 重试机制装饰器import time from functools import wraps def retry(max_attempts3, delay1, exceptions(Exception,)): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, max_attempts 1): try: return func(*args, **kwargs) except exceptions as e: print(f第{attempt}次尝试失败: {e}) if attempt max_attempts: raise time.sleep(delay) return None return wrapper return decorator retry(max_attempts5, delay2, exceptions(ConnectionError, TimeoutError)) def fetch_data(): import random if random.random() 0.8: raise ConnectionError(网络连接失败) return 数据内容9.4 缓存装饰器简单LRUPython内置的functools.lru_cache就是最好的例子我们也可以自己实现一个简单的版本from functools import wraps def simple_cache(func): cache {} wraps(func) def wrapper(*args, **kwargs): # 为了简化将参数转为tuple作为key不处理kwargs中的可变对象 key args tuple(sorted(kwargs.items())) if key not in cache: print(f计算新结果: {func.__name__}{args}) cache[key] func(*args, **kwargs) else: print(f从缓存读取: {func.__name__}{args}) return cache[key] return wrapper simple_cache def fibonacci(n): if n 2: return n return fibonacci(n-1) fibonacci(n-2) print(fibonacci(10)) # 中间结果会被缓存加速递归计算9.5 日志记录装饰器import logging from functools import wraps logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) def log(levellogging.INFO): def decorator(func): wraps(func) def wrapper(*args, **kwargs): logging.log(level, f调用 {func.__name__} 参数: args{args}, kwargs{kwargs}) try: result func(*args, **kwargs) logging.log(level, f{func.__name__} 返回: {result}) return result except Exception as e: logging.exception(f{func.__name__} 异常: {e}) raise return wrapper return decorator log(levellogging.INFO) def divide(a, b): return a / b divide(10, 2) divide(10, 0) # 会记录异常9.6 单例模式装饰器使用类装饰器实现单例模式def singleton(cls): instances {} wraps(cls) def get_instance(*args, **kwargs): if cls not in instances: instances[cls] cls(*args, **kwargs) return instances[cls] return get_instance singleton class DatabaseConnection: def __init__(self): print(初始化数据库连接仅一次) self.connected True db1 DatabaseConnection() # 输出初始化信息 db2 DatabaseConnection() # 无输出返回同一个实例 print(db1 is db2) # True10. 总结与最佳实践10.1 装饰器核心要点回顾1. 本质装饰器是接收函数并返回新函数的高阶函数。2. 闭包依赖闭包来“记忆”被装饰函数。3. 参数处理使用*args, **kwargs确保通用性。4. 返回值wrapper必须返回原函数的返回值。5. 元信息始终使用functools.wraps保留原函数元数据。6. 嵌套层级- 无参数装饰器2层装饰器函数 wrapper- 带参数装饰器3层参数接收层 装饰器层 wrapper10.2 常见错误与陷阱- 忘记返回wrapper导致装饰后函数变成None。- 不保留args, kwargs无法装饰带参数的函数。- 忽略返回值调用func后忘记return。- 多个装饰器的顺序错误例如在login_required之后使用timer可能导致计时包含重定向逻辑。- 修改可变默认参数装饰器中的列表/字典作为默认参数会被多次调用共享。10.3 何时使用装饰器- 横切关注点Aspect-Oriented Programming日志、性能计时、事务管理、权限校验、缓存、重试。- 函数注册如Flask的路由注册、插件系统。- 输入验证/类型检查在函数执行前校验参数类型。- 同步/异步转换将同步函数转为异步执行需配合线程池。装饰器是Python中一道分水岭理解装饰器意味着你从“会用Python”进阶到“理解Python的设计哲学”。希望本文的详细讲解和大量代码示例能够帮助你彻底掌握装饰器。在未来的开发中善用装饰器可以让你的代码更加优雅、可维护。