news 2026/10/15 19:04:45

深入理解 Python 装饰器:从基本语法到自定义带参装饰器(c9-python-getting-started 实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解 Python 装饰器:从基本语法到自定义带参装饰器(c9-python-getting-started 实战)
  • 示例工程
  • 教程

【免费下载链接】c9-python-getting-started

Sample code for Channel 9 Python for Beginners course

项目地址:https://gitcode.com/gh_mirrors/c9/c9-python-getting-started
点击查看免费下载

导读:装饰器(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;只有少数时候你才需要成为装饰器的"创建者",去封装自己的通用逻辑。

这决定了学习的优先级:

  1. 先学会正确使用:理解@语法、理解装饰器会包装被装饰函数,调用时执行的是包装后的版本;
  2. 再掌握创建方法:当你要做日志记录、性能计时、权限校验、重试机制等跨函数通用能力时,自定义装饰器就是最优雅的封装手段。

仓库中的 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,执行顺序为:

  1. print(color + 'changing to blue')——在Fore.BLUE(ANSI 蓝色转义序列)作用下输出带颜色的提示;
  2. func(*args, **kwargs)——真正调用原来的greeter,依次打印Hello, world!!与Just saying hi again。

这就是装饰器的核心行为模式:在原有函数执行之前(或之后)插入额外逻辑。本例中print位置在func()之前,属于"前置增强";若把print移到func()之后,就变成"后置增强",可用于记录函数执行结果、耗时等场景。

如何运行与验证

要在本地复现这个示例,步骤如下:

  1. 安装依赖:示例依赖colorama,该依赖已在课程 15 - Packages/requirements.txt 中声明,可执行pip install -r "python-for-beginners/15 - Packages/requirements.txt"或直接pip install colorama;
  2. 运行脚本:python "python-for-beginners/18 - Decorators/creating_decorators.py";
  3. 观察输出:终端会先出现一行蓝色的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

项目地址:https://gitcode.com/gh_mirrors/c9/c9-python-getting-started
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 19:04:32

Maya 2018.6 DEVKIT插件编译指南:从环境配置到首个.mll

简介:Autodesk Maya 2018.6 Update DEVKIT Windows 版是一套面向三维工具开发者的官方开发套件,适合熟悉 C、Python 或 C# 的程序员,用于编写自定义插件、扩展节点、自动化工作流以及集成渲染管线。压缩包共含 6297 个文件,主体为…

作者头像 李华
网站建设 2026/10/15 19:04:27

BeefTV 画布完全指南:节点、连线与自由创作工作流

人工智能AI 应用AI Agent媒体生成音视频桌面应用 【免费下载链接】BeefTV High-performance Lightweight AI Native Video workspace. 面向 AI 时代的视频创作工作台。 在画布中,和Agent一起自由连接创意、模型与素材。 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/10/15 19:04:05

DeepSeek生成的内容能否下载吗?

DeepSeek生成的内容能否下载吗? 一个被多数人忽略的真相:AI 对话框里的内容,本质上并不属于你 这个问题在搜索引擎里被反复问起,但大多数回答都停留在“截图”或“复制粘贴”的层面。作为长期研究 AI 工作流效率的从业者&#xff…

作者头像 李华
网站建设 2026/10/15 19:03:05

Web Clipper 开源通用网页剪藏工具:安装、开发与架构全解析

开发工具 【免费下载链接】web-clipper For Notion,OneNote,Bear,Yuque,Joplin。Clip anything to anywhere 项目地址: https://gitcode.com/gh_mirrors/we/web-clipper 点击查看 免费下载 本文围绕开源仓库 web-clipper 的核心文档展开,系统讲解这款“…

作者头像 李华
网站建设 2026/10/15 19:02:44

参考资源不是链接,而是一套方法论

写到第32章,我想换个话题,认真聊聊"参考资源"这四个字。前31章的内容,几乎每一章都会出现类似于"详细的接口说明可以查阅官方文档""这位同行的实现在某社区有详细讨论""补课的读者可以先看某某基础教程&q…

作者头像 李华