Python 装饰器很难?那是你没看到这篇文章!

简介: Python装饰器是高效复用代码的利器!本文用通俗语言拆解其本质:从函数是一等公民、闭包原理,到手写计时装饰器、@语法糖、支持参数的万能版,再到保留元信息的完美实践。零基础也能轻松掌握!

大家好,我是 Alex。很多小伙伴在学习 Python 时,都有一座很多初学者觉得难以翻越的大山,那就是 装饰器 (Decorator)。

每当你看到代码里那个神秘的 @ 符号,是不是总觉得心里没底?面试官问你“什么是闭包,什么是装饰器”时,是不是支支吾吾答不上来?

别担心,今天我们就把高深的概念扔到一边,用最通俗的语言,由浅入深,一层一层剥开装饰器的面纱。看完这一篇,保证你以后写代码时,想不用装饰器都难!


01 为啥我们需要装饰器?

在讲技术之前,我们先来看一个实际业务场景。

假设你正在开发一个项目,里面有很多重要的业务函数,比如“处理订单”、“导出报表”等。现在老板提了一个需求:“给每个业务函数都加上日志记录,我想知道每个函数执行了多久。”

作为一个老实人,你可能会这样做:

import time

def process_order():
    start_time = time.time()  # 记录开始时间
    print("正在处理订单...")
    time.sleep(1)             # 模拟业务耗时
    end_time = time.time()    # 记录结束时间
    print(f"耗时: {end_time - start_time} 秒")

def export_report():
    start_time = time.time()  # 重复代码...
    print("正在导出报表...")
    time.sleep(2)
    end_time = time.time()    # 重复代码...
    print(f"耗时: {end_time - start_time} 秒")

写完后你就会发现,代码里充斥着大量重复的计时逻辑。如果老板明天说“耗时要保留两位小数”,你得把几十个函数全改一遍。这简直是维护的噩梦!😱

然鹅,我们真正想要的是:

  1. 不修改原有的 process_order 函数内部代码。
  2. 在它执行前后,自动加上计时的功能。

这时候,当当当,装饰器就该闪亮登场了。


02 一切的基础:函数也是“东西”

在 Python 中,函数是一等公民(First Class Citizen)。这话说得太学术,简单来说就是:函数和变量一样,可以被传来传去。

  • 函数可以赋值给变量。
  • 函数可以作为参数传给另一个函数。
  • 函数里可以定义另一个函数,并且把它返回。 (注意,这是一个重点!)

看个简单的例子:

def outer():
    print("我是外层函数")

    def inner():
        print("我是内层函数")

    return inner  # 注意:这里返回的是函数对象,没有加括号()

# 调用
func = outer() # 执行 outer,拿到 inner
func()         # 执行 inner

理解了这一点,我们就可以大致理解了什么事装饰器了。


03 打造第一个装饰器

装饰器的本质,其实就是一个闭包。

我们可以把它想象成一块“夹心饼干”。

  • 原函数(比如 process_order)是饼干的夹心。
  • 装饰器是在外面包的一层酥皮(添加的新功能)。

下面,我们来写一个专门用来计时的装饰器 timer:

import time

# 这就是装饰器函数
def timer(func):
    # wrapper 是包装纸,把原函数包起来
    def wrapper():
        print(f"--- 开始执行 {func.__name__} ---")
        start_time = time.time()

        func()  # 这里是真正的业务逻辑(夹心)

        end_time = time.time()
        print(f"--- 执行完毕,耗时: {end_time - start_time:.2f} 秒 ---")

    return wrapper  # 把包装好的新函数送回去

现在,我们要用这个 timer 来“装饰”我们的业务函数:

def process_order():
    print("正在处理订单...")
    time.sleep(1)

# 手动装饰
process_order = timer(process_order)
# 当然了,如果你觉得这里的 process_order 有歧义的话,你完全可以命名成其他的名字,比如: 
# process_order123 = timer(process_order)
# 那么调用的时候就是 process_order123()

# 再次调用 process_order,其实是在调用 wrapper
process_order()

运行结果:

--- 开始执行 process_order ---
正在处理订单...
--- 执行完毕,耗时: 1.00 秒 ---

看!我们没有改动 process_order 的任何一行代码,却给它加上了计时功能。这就是装饰器的雏形。


04 那个神奇的 @ 符号

虽然上面的 process_order = timer(process_order) 也能用,但写起来不够优雅。有的童鞋也就有疑问了,在 Python 的一些框架中,也没见这么用哇,都是直接一个 @ 符号就搞定了,比如,定义路由的时候。

是的,Python 贴心地为我们提供了语法糖(Syntactic Sugar),也就是那个 @ 符号。

下面的代码,和上面的手动赋值是完全等价的:

# 下面的 @timer 就相当于执行了: process_order = timer(process_order)
@timer
def process_order():
    print("正在处理订单...")
    time.sleep(1)

process_order()

是不是瞬间清爽了许多?


05 进阶:如果原函数有参数怎么办?

上面的装饰器有个致命弱点:wrapper 函数没有定义参数。如果原本的 process_order 需要传参,程序就会报错。

为了做一个万能的装饰器,我们需要用到 *args 和 **kwargs。这两个兄弟可以代表“任意数量、任意类型的参数”。

升级版装饰器 v2.0:

def timer_pro(func):
    # 接收任意参数
    def wrapper(*args, **kwargs):
        print(">>> 计时开始")
        start = time.time()

        # 把接收到的参数,原封不动地传给原函数
        # 并且接收原函数的返回值
        result = func(*args, **kwargs) 

        end = time.time()
        print(f">>> 计时结束,耗时 {end - start:.4f} 秒")

        # 千万别忘了把原函数的返回值送回去!
        return result

    return wrapper

@timer_pro
def add(a, b):
    time.sleep(0.5)
    return a + b

# 测试
sum_val = add(10, 20)
print(f"计算结果: {sum_val}")

💡 敲黑板:
在写装饰器时,记得要在 wrapper 内部返回 result。否则,你的 add 函数虽然执行了,但外面拿到的结果会是 None(因为被装饰器“吃掉”了)。


06 完美主义:保留函数的特征

到这里,装饰器已经能处理 99% 的情况了。但是还有一个小瑕疵。

如果你打印 add.__name__(查看函数名),你会发现它竟然变成了 wrapper!

print(add.__name__) 
# 输出: wrapper
# 期望: add

这在调试或者生成文档时会很麻烦,相当于原函数的“身份证”被篡改了。为了解决这个问题,我们需要引入 Python 标准库中的 functools.wraps。

最终完美版装饰器 v3.0:

from functools import wraps
import time

def timer_final(func):
    @wraps(func)  # <--- 这一行是关键!它负责把原函数的信息复制给 wrapper
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 耗时: {end - start:.4f}s")
        return result
    return wrapper

@timer_final
def sub(a, b):
    """这是一个减法函数"""
    return a - b

print(sub.__name__)  # 输出: sub
print(sub.__doc__)   # 输出: 这是一个减法函数

现在,这个装饰器不仅功能强大,而且“无痕”,完全保留了原函数的元数据。这才是专业 Python 开发者该有的写法。😎


总结一下

Python 装饰器其实并不神秘,它就是一种设计模式:

  1. 不改变原函数代码:符合“开闭原则”(对扩展开放,对修改关闭)。
  2. 闭包:函数嵌套函数,内层函数引用外层变量。
  3. 应用场景:日志记录、性能测试、权限校验(比如 Flask 中的 @login_required)、缓存数据等。

希望这篇文章能帮你彻底拿下装饰器!

如果你觉得有用,记得点赞、收藏、分享哦,笔芯 ❤️

相关文章
|
5月前
|
Rust Linux 开发者
再见 pip!Rust 写的 uv 正在把 Python 包管理按在地上摩擦
Python开发者最头疼的依赖管理与环境配置难题,终于有解!uv——Rust编写的超快包管理器,安装、解析、虚拟环境创建速度达pip的10-100倍。支持一键装Python版本、运行脚本、编译依赖,正重塑Python开发工作流。(239字)
494 1
|
5月前
|
安全 Java 编译器
【Java基础】面向对象三大特性、重载 vs 重写、接口 vs 抽象类(附《面试背诵版》+《自测题》)
本文系统梳理Java面向对象核心知识:详解封装、继承、多态三大特性及实现要点;清晰对比重载(编译时多态)与重写(运行时多态)的语法与语义差异;深入剖析抽象类(is-a、代码复用)与接口(can-do、多实现)的本质区别;涵盖面试高频考点、典型易错点及速记口诀,助你高效掌握并应对技术面试。
|
5月前
|
人工智能 安全 搜索推荐
AI技术在少儿英语学习中的应用
AI正推动少儿英语学习迈入“智能体时代”:针对3–12岁儿童注意力短、发音稚嫩、认知具象等特点,打造多模态虚拟外教、AI互动绘本、童音定制评测、动态生成故事四大核心场景,深度融合SLA教学理论、儿童专用语音模型与内容安全护栏,并通过家长端AI成长报告实现教育闭环。(239字)
|
5月前
|
人工智能 机器人 API
Hermes Agent是什么?本地+云端+Docker全平台部署与阿里云百炼接入实操手册
Hermes Agent是由Nous Research开发的开源自主AI智能体框架,遵循MIT开源协议,核心定位是打造具备持久记忆、自我进化、多工具调用与跨平台接入能力的“数字员工”。它并非简单的聊天机器人,而是能自主规划任务、沉淀技能、跨会话召回记忆的智能执行体,真正实现“越用越聪明”。
794 5
|
6月前
|
存储 人工智能 NoSQL
让 Agent 拥有记忆 —— 表格存储记忆服务邀测指南
本文将介绍表格存储记忆服务的产品能力、接入方式和接口说明,帮助您快速了解和体验表格存储记忆服务的相关功能。
954 2
|
6月前
|
人工智能 监控 Kubernetes
LoongCollector + ACS Agent Sandbox:构建 AI Agent 生产级运行平台
文章介绍了阿里云ACSAgentSandbox与LoongCollector协同构建的AIAgent生产级运行平台,通过沙箱隔离保障运行时安全,并以高性能、全链路可观测能力解决Agent行为不可预测和执行风险难题。
3522 100
|
6月前
|
监控 网络协议 网络安全
RUM 实战:用数据说话的 Android 网络性能优化
移动端网络性能直接影响用户体验,面临网络多样、设备碎片化、问题难复现、监控粗粒度等挑战。阿里云 RUM Android SDK 通过采集详细的网络资源指标,助力开发者精准定位性能瓶颈。
420 46
|
5月前
|
人工智能 运维 安全
让 AI 帮你运维 Elasticsearch:阿里云 ES Agent Skill 正式发布
阿里云Elasticsearch Agent Skill是一套面向AI编程助手的智能运维技能包,覆盖实例创建、故障诊断、网络配置三大核心场景。支持自然语言交互,自动校验参数、识别架构差异、执行幂等操作,并内置49条诊断规则与7套SOP,大幅提升ES运维效率与可靠性。
911 7
|
5月前
|
安全 程序员 API
初级程序员必备的十大技能之规范编码与团队协作(二)
教程来源 http://oplhc.cn/ 本指南系统梳理代码审查(Code Review)核心实践:涵盖PR规范、多维检查清单(功能/质量/性能/安全/测试/文档)、建设性评论技巧、健康协作心态,以及README、代码注释、CHANGELOG等文档编写标准,助力团队高效交付高质量、可维护、安全的代码。
|
5月前
|
存储 人工智能 固态存储
阿里云4核云服务器租用价格解析:4核8G、4核16G、4核32G配置最新收费标准与活动价格
本文介绍了阿里云4核云服务器的配置选择、价格体系及购买策略。4核配置涵盖经济型e实例、通用算力型u2i/u2a、计算型c9i/c9a、通用型g9及内存型r9等多个实例族,分别适用于个人博客、企业Web应用、AI推理及大数据处理等场景。同时,文中列出了4核8G、16G、32G在各实例下的官方标准价及2026年活动价(如u2i实例4核8G低至1252.63元/年起)。建议用户根据业务需求选型,结合优惠券实现折上折,有效降低上云成本。

热门文章

最新文章