- 示例工程
- 教程
【免费下载链接】c9-python-getting-started
Sample code for Channel 9 Python for Beginners course
导读:装饰器(Decorator)是 Python 中最具标志性的语法特性之一,它以@符号为代码块附加含义与功能,是 Flask、Django 等 Web 框架的路由注册、权限校验等能力的地基。本文以 python-for-beginners 课程仓库 中的第 18 章「Decorators」为核心,结合仓库内可运行的 creating_decorators.py 源码,带你从"会用装饰器"走向"手写一个支持参数的装饰器",并理解其背后的闭包与包装机制。
装饰器是什么:为代码块附加能力的语法糖
Python 官方通过 PEP 318 给出了一个非常直观的定位:装饰器与"属性(attributes)"类似,都是为 Python 中的代码块附加含义或功能的手段。如果说属性是给对象挂上数据与状态,那么装饰器就是给函数(或类)挂上行为与能力,而且这种挂载发生在定义阶段,调用方无感知。
装饰器最典型的形态是@语法,紧贴在函数定义上方:
# Example decorator @log(True) def sample_function(): print('this is a sample function')这是课程文档中给出的示例。注意这里的log(True)并不是一个装饰器本身,而是一个带参数的装饰器调用——它先返回一个真正的装饰器,再由该装饰器作用于sample_function。这种"函数返回函数"的写法,是理解装饰器的第一道关口,也是后文仓库实战代码的核心结构。
为什么装饰器在框架中如此常见
课程文档明确指出:装饰器"frequently used in frameworks such as Flask or Django"。这一点在实际开发中很容易验证:
- Flask用
@app.route('/')把 URL 路径与处理函数绑定; - Django用
@login_required、@require_http_methods(["POST"])等为视图函数附加认证、请求方法限制等横切逻辑。
这些装饰器做的事情本质相同:在不修改函数内部代码的前提下,为函数增加注册、校验、日志等额外行为。这正是"面向切面"思想的体现——把与业务无关的公共逻辑抽离出来,通过声明式语法挂在函数上,让业务函数本身保持干净、专注。
使用装饰器与创建装饰器:两种身份
课程文档特别提醒初学者一个事实:"The most common interaction you'll have with decorators as a Python developer is through using them rather than creating them."——在日常开发中,绝大多数情况下你是装饰器的"使用者",比如写 Flask 路由时用@app.route;只有少数时候你才需要成为装饰器的"创建者",去封装自己的通用逻辑。
这决定了学习的优先级:
- 先学会正确使用:理解
@语法、理解装饰器会包装被装饰函数,调用时执行的是包装后的版本; - 再掌握创建方法:当你要做日志记录、性能计时、权限校验、重试机制等跨函数通用能力时,自定义装饰器就是最优雅的封装手段。
仓库中的 creating_decorators.py 正是"创建者"视角的完整范例,下面逐层拆解它。
仓库实战源码解析:一个带参数的彩色输出装饰器
先看完整源码(creating_decorators.py):
import functools from colorama import init, Fore init() def color(color): def wrapper(func): @functools.wraps(func) def runner(*args, **kwargs): print(color + 'changing to blue') func(*args, **kwargs) return runner return wrapper @color(color=Fore.BLUE) def greeter(): print('Hello, world!!') print('Just saying hi again') greeter()这个示例虽然只有二十余行,却几乎涵盖了自定义装饰器的全部核心知识点。它用到两个第三方/标准库能力:
- colorama:为终端输出添加 ANSI 颜色的第三方包,该课程在 15 - Packages/requirements.txt 中已声明依赖,并在 15 - Packages/color_import_demo.py 中演示过
Fore.BLUE等颜色常量的用法; - functools:标准库,这里用到了
functools.wraps来保留被装饰函数的元信息。
三层嵌套结构:装饰器工厂
这个例子是"带参数的装饰器",因此需要三层嵌套,从外到内依次是:
| 层级 | 函数 | 职责 |
|---|---|---|
| 第 1 层 | color(color) | 接收装饰器参数(这里是颜色Fore.BLUE),返回真正的装饰器wrapper |
| 第 2 层 | wrapper(func) | 接收被装饰的函数greeter,返回包装函数runner |
| 第 3 层 | runner(*args, **kwargs) | 真正替代原函数执行的新函数,先输出提示信息,再调用原函数 |
之所以需要三层,是因为@color(color=Fore.BLUE)这种带括号的写法等价于:
greeter = color(color=Fore.BLUE)(greeter)即:先调用color(Fore.BLUE)得到装饰器wrapper,再用wrapper(greeter)得到包装后的函数。外层color的作用就是把参数"记住"(通过闭包),供内层使用。
闭包:被记住的参数与函数
color返回wrapper,wrapper又返回runner,每一层都能访问上一层作用域的变量:
runner内部访问了外层color的参数color(即Fore.BLUE);- 同时通过参数
func持有了被装饰的greeter函数对象。
这种"内层函数捕获外层函数变量"的机制叫闭包(closure)。闭包是装饰器能够工作的底层原理:被装饰函数及其配置参数被"冻结"在包装函数的作用域链中,即使外层函数早已返回,runner依然可以使用它们。
*args, **kwargs:把参数原样透传
runner(*args, **kwargs)中的*args与**kwargs用于接收任意位置参数与关键字参数,并在包装内部原样转交给原函数:func(*args, **kwargs)。
这意味着这个装饰器是通用的——不管被装饰的函数签名如何(零参数、多参数、带默认值),包装层都能无障碍透传。如果不写透传,被装饰函数一旦带参数,调用就会失败,这也是新手自定义装饰器最常踩的坑。
functools.wraps:保住函数的"身份"
@functools.wraps(func)作用于runner,作用是把原函数func的元信息(如__name__、__doc__、__module__)复制到包装函数上。
原因在于:greeter = wrapper(greeter)之后,模块中真正的greeter名字指向的是runner。若不处理,greeter.__name__会变成'runner',导致调试、日志、文档工具看到的是错误的函数名。functools.wraps让包装后的函数"看起来"仍然是原来的函数,这在构建可维护的框架级代码时至关重要。
运行效果:装饰器在调用时"抢跑"
greeter()位于文件末尾,调用它时实际执行的是runner,执行顺序为:
print(color + 'changing to blue')——在Fore.BLUE(ANSI 蓝色转义序列)作用下输出带颜色的提示;func(*args, **kwargs)——真正调用原来的greeter,依次打印Hello, world!!与Just saying hi again。
这就是装饰器的核心行为模式:在原有函数执行之前(或之后)插入额外逻辑。本例中print位置在func()之前,属于"前置增强";若把print移到func()之后,就变成"后置增强",可用于记录函数执行结果、耗时等场景。
如何运行与验证
要在本地复现这个示例,步骤如下:
- 安装依赖:示例依赖
colorama,该依赖已在课程 15 - Packages/requirements.txt 中声明,可执行pip install -r "python-for-beginners/15 - Packages/requirements.txt"或直接pip install colorama; - 运行脚本:
python "python-for-beginners/18 - Decorators/creating_decorators.py"; - 观察输出:终端会先出现一行蓝色的
changing to blue,随后是原函数的Hello, world!!与Just saying hi again。
你也可以做几个小实验来加深理解:
- 把
print(color + 'changing to blue')注释掉,观察greeter()是否仍正常输出——验证装饰器对原函数调用的透传; - 给
greeter增加参数(如def greeter(name)),由于runner有*args, **kwargs透传,装饰器无需改动即可正常工作; - 去掉
@functools.wraps(func)后打印greeter.__name__,对比前后差异,直观感受元信息被覆盖的问题。
从装饰器出发:常见应用场景与进阶方向
掌握了"三层嵌套 + 闭包 + 透传 +functools.wraps"这套组合拳,你就可以封装大量通用能力:
- 日志记录:在函数调用前后打印入参与返回结果;
- 性能计时:记录函数执行耗时,用于性能分析;
- 权限校验:如 Django 的
@login_required,先检查身份再放行; - 重试与缓存:失败自动重试、结果缓存复用;
- 框架路由:如 Flask 的
@app.route('/'),把函数注册进路由表。
需要注意的是,本仓库的定位是"Python 入门课程"(见 python-for-beginners/README.md),课程明确不深入类设计、继承与异步编程,装饰器章节也以"理解概念 + 会用 + 会写基础装饰器"为目标。若你想继续深入,可以沿着两个方向探索:一是研究 Flask/Django 源码中装饰器的实际写法,二是学习类装饰器与functools.wraps的完整实现细节。
小结
- 装饰器由 PEP 318 引入,本质是为代码块附加含义与功能的语法糖,与"属性"为对象附加数据的思路一脉相承;
- 日常开发中,使用装饰器多于创建装饰器,Flask 的
@app.route、Django 的@login_required都是典型代表; - 创建装饰器的完整模型是"装饰器工厂 → 装饰器 → 包装函数"的三层结构,仓库中的 creating_decorators.py 是理解这一模型的最佳起点;
- 三个必须养成的习惯:用
*args, **kwargs透传参数、用functools.wraps(func)保留函数元信息、把增强逻辑放在func()调用之前或之后以控制执行时机。
把@log(True)的例子亲手跑一遍,再对照creating_decorators.py逐层修改,你就能真正从"装饰器使用者"进阶为"装饰器创建者"。
- 示例工程
- 教程
【免费下载链接】c9-python-getting-started
Sample code for Channel 9 Python for Beginners course
相关推荐
深入 Python 装饰器实战:从 `logged` 到注解校验 `validated` 装饰器(Advanced Python Mastery Exercise 7.1)
深入 Python 装饰器实战:从 logged 到注解校验 validated 装饰器(Advanced Python Mastery Exercise 7.
示例工程教程Midway 3.0 自定义装饰器完全指南:类装饰器、属性装饰器、方法装饰器与参数装饰器实战
Midway 3.0 自定义装饰器完全指南:类装饰器、属性装饰器、方法装饰器与参数装饰器实战 导读 Midway 3.0 提供了一套由框架托管的自定义装饰器能力
后端微服务云原生TypeGraphQL 自定义装饰器完全指南:从方法装饰器到参数注入的实战方案
TypeGraphQL 自定义装饰器完全指南:从方法装饰器到参数注入的实战方案 导读 本文聚焦 TypeGraphQL 中「自定义装饰器」(Custom Dec
后端GraphQLAPI设计
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考