0

0

Python中如何使用装饰器?语法与应用场景

星夢妙者

星夢妙者

发布时间:2025-07-02 16:56:01

|

353人浏览过

|

来源于php中文网

原创

python中的装饰器是一种特殊语法糖,用于在不修改原有函数或类代码的情况下为其添加额外功能。它本质上是一个高阶函数,接受函数作为参数并返回新函数。使用@符号实现简洁的装饰方式,例如@timer为函数添加计时功能。装饰器的核心价值在于非侵入性和可重用性,适用于日志记录、权限验证、缓存等场景。编写自定义装饰器时需注意:1. 使用functools.wraps保留函数元数据;2. 带参数的装饰器需嵌套三层函数结构;3. 多个装饰器按自下而上顺序应用;4. 类装饰器可用于修改或替换整个类;5. 描述符和元类是更底层的“装饰”机制。这些特性使装饰器成为解决横切关注点、提升代码可维护性的强大工具

Python中如何使用装饰器?语法与应用场景

Python中的装饰器,简单来说,就是一种特殊语法糖,它允许你在不修改原有函数或类代码的情况下,为它们添加额外的功能或修改其行为。你可以把它想象成一个包装器,在函数执行前后做点什么,或者完全改变函数的功能,而这一切都发生在函数定义的时候。它本质上是一个接受函数作为参数并返回一个新函数的高阶函数。

Python中如何使用装饰器?语法与应用场景

解决方案

要使用装饰器,最直接的方式就是利用Python提供的@语法糖。这让代码看起来非常简洁和直观。

Python中如何使用装饰器?语法与应用场景

比如说,我们想给一个函数添加一个计时功能,看看它运行了多久。我们可以这样写一个装饰器:

立即学习Python免费学习笔记(深入)”;

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()
        print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")
        return result
    return wrapper

@timer
def long_running_task(n):
    """一个模拟长时间运行的任务"""
    sum_val = 0
    for i in range(n):
        sum_val += i * i
    return sum_val

# 调用被装饰的函数
long_running_task(1000000)
# 输出: 函数 'long_running_task' 执行耗时: 0.0450 秒 (类似)

在这个例子里,@timer 放在 long_running_task 定义的上方,等价于 long_running_task = timer(long_running_task)timer 函数接收 long_running_task 作为参数,然后返回一个新的 wrapper 函数。当我们调用 long_running_task(1000000) 时,实际执行的是 wrapper 函数,它在调用原始 long_running_task 前后加上了计时逻辑。

Python中如何使用装饰器?语法与应用场景

装饰器不仅限于函数,也可以装饰类,但那通常涉及类装饰器,概念上更进一步。核心思想都是通过包装来扩展或修改行为。

装饰器在实际开发中能解决哪些痛点?

在我看来,装饰器最核心的价值在于它的“非侵入性”和“可重用性”。想象一下,如果你有几十个函数都需要记录日志、检查权限或者做性能监控,难道你要在每个函数内部都写一遍重复的代码吗?那简直是噩梦。装饰器就是为了解决这类横切关注点(cross-cutting concerns)而生的。

首先,日志记录和性能监控是它的经典应用。你不需要在每个业务逻辑函数里都塞满print或者logging.info,也不用手动计算time.time()。一个简单的@log_calls或者@timer就能搞定,代码干净利落。这让我们的业务逻辑层保持纯粹,只关注“做什么”,而不是“如何被监控”。

其次,权限验证和身份认证也是装饰器大展身手的地方。比如一个Web框架,很多视图函数可能需要用户登录后才能访问,或者需要特定角色才能操作。你可以在这些视图函数上加上@login_required@permission_required('admin')。这样,当请求到达时,装饰器会先检查用户状态和权限,如果不满足条件就直接返回错误,避免了在每个视图函数开头都写一大段权限判断逻辑。这极大地提高了代码的安全性、可维护性,并且避免了漏掉某个权限检查的低级错误。

再者,缓存也是一个非常实用的场景。如果某个函数的计算成本很高,但其结果在一段时间内是稳定的,我们就可以用装饰器来缓存它的返回值。@cache 装饰器可以检查是否有缓存结果,有就直接返回,没有才真正执行函数,然后把结果存入缓存。这对于提升API响应速度、减轻数据库压力等都非常有效。例如Python标准库中的@functools.lru_cache就是一个很好的例子,它会自动处理缓存的过期和淘汰。

这些应用场景,无一不体现了装饰器将通用逻辑与特定业务逻辑解耦的能力。它就像一个工具箱,里面装着各种小工具,你随时可以拿出来给你的函数“升级”,而不用去拆开函数内部的“发动机”。

编写自定义装饰器时,有哪些常见的陷阱或需要注意的地方?

虽然装饰器用起来很爽,但自己写的时候,确实有些地方一不小心就容易踩坑,或者说,有些最佳实践是必须掌握的。

我个人觉得最常见也最重要的一个坑,就是被装饰函数的元数据丢失问题。如果你直接写一个简单的装饰器,像这样:

闪念贝壳
闪念贝壳

闪念贝壳是一款AI 驱动的智能语音笔记,随时随地用语音记录你的每一个想法。

下载
def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("执行前...")
        result = func(*args, **kwargs)
        print("执行后...")
        return result
    return wrapper

@my_decorator
def greet(name):
    """一个打招呼的函数"""
    return f"Hello, {name}"

# print(greet.__name__) # 结果是 'wrapper'
# print(greet.__doc__)  # 结果是 None

你会发现 greet.__name__ 变成了 wrappergreet.__doc__ 也丢失了。这在调试、文档生成或者某些框架(如测试框架)依赖函数元数据时,会造成很大的困扰。解决办法就是使用 functools.wraps。它会将被装饰函数的 __name____doc____module____annotations__ 等属性复制到包装器函数上。

import functools

def my_decorator_fixed(func):
    @functools.wraps(func) # 加上这一行
    def wrapper(*args, **kwargs):
        print("执行前...")
        result = func(*args, **kwargs)
        print("执行后...")
        return result
    return wrapper

@my_decorator_fixed
def greet_fixed(name):
    """一个打招呼的函数"""
    return f"Hello, {name}"

# print(greet_fixed.__name__) # 结果是 'greet_fixed'
# print(greet_fixed.__doc__)  # 结果是 '一个打招呼的函数'

另一个需要注意的,是带参数的装饰器。当你需要装饰器本身也接收参数时,就需要再嵌套一层函数。这会让新手有点晕,但理解了就清晰了:

def repeat(num_times): # 装饰器工厂函数,接收参数
    def decorator(func): # 真正的装饰器,接收函数
        @functools.wraps(func)
        def wrapper(*args, **kwargs): # 包装器,执行逻辑
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3) # 这里的 repeat(3) 返回的是 decorator 函数
def say_hello():
    print("Hello!")

say_hello()
# 输出三次 "Hello!"

这里 repeat(3) 并不是装饰器本身,它是一个“装饰器工厂”,执行后返回一个真正的装饰器 decorator,然后 @decorator 才去装饰 say_hello

此外,多个装饰器的应用顺序也需要留意。装饰器是自下而上应用的,但执行时是自上而下。这意味着离函数定义最近的装饰器(最下面那个)会最先被“包装”,最上面那个装饰器会是“最外层”的包装器。

@decorator_a
@decorator_b
def my_function():
    pass

这等价于 my_function = decorator_a(decorator_b(my_function))。理解这个顺序对于调试和预期行为至关重要。

最后,类装饰器虽然不常用,但它能直接修改或替换整个类。这比函数装饰器更强大,也更复杂。通常用于框架层面的元编程,比如ORM框架中定义模型、注册组件等。理解它的原理需要对Python的类创建过程有更深的认识。

除了函数装饰器,Python还有哪些相关的“装饰”机制?

当我们谈到Python的“装饰”机制,函数装饰器无疑是最常见、最直接的。但如果把视野放宽一些,你会发现Python在很多地方都体现了这种“在不修改源代码的前提下,增强或改变行为”的思想。

最直接的扩展就是类装饰器。它和函数装饰器非常相似,只不过它装饰的对象是一个类。一个类装饰器接收一个类作为参数,然后返回一个新的类(通常是修改后的原类)。这在需要对类进行统一处理,比如自动添加方法、修改类的属性、注册类到某个系统等场景下非常有用。

def add_timestamp(cls):
    """一个类装饰器,给类添加创建时间属性"""
    setattr(cls, 'created_at', '2023-10-27 10:00:00') # 示例,实际应是动态时间
    return cls

@add_timestamp
class MyDataModel:
    def __init__(self, name):
        self.name = name

# print(MyDataModel.created_at) # 输出 '2023-10-27 10:00:00'

这里 add_timestamp 装饰器直接修改了 MyDataModel 类,为它增加了一个 created_at 类属性。类装饰器常用于框架层面,例如Django的admin.register装饰器,或者一些依赖注入框架。

再深入一点,描述符(Descriptors)也是一种非常强大的“装饰”机制。它们是实现了特定协议(__get__, __set__, __delete__方法)的类实例,当它们作为类属性被访问时,会拦截属性的访问行为。propertyclassmethodstaticmethod这些内置的装饰器,其底层就是通过描述符实现的。

  • property 允许你把方法当做属性来访问,实现了属性的“计算”和“验证”逻辑,而外部看起来依然是直接访问属性。
  • classmethod 让方法绑定到类而不是实例,第一个参数是类本身(cls)。
  • staticmethod 更是完全不绑定到类或实例,只是一个放在类命名空间下的普通函数。

这些都是通过描述符机制,在不改变你调用方式(obj.attrClass.method())的前提下,改变了它们实际的行为。

最后,虽然不是直接的“装饰器”,但元类(Metaclasses)在更高维度上实现了对类的“装饰”或“定制”。元类是创建类的类。通常情况下,我们创建类时,Python会使用默认的type元类。但如果你定义一个自己的元类,你就可以在类被创建时,控制它的所有行为,比如自动添加基类、检查方法签名、甚至动态生成方法。这比类装饰器更底层、更强大,也更复杂,一般只在构建非常灵活和可扩展的框架时才会用到。

所以,从函数装饰器到类装饰器,再到描述符和元类,Python提供了一系列不同层次的工具,让我们能够以非常优雅和非侵入的方式,对代码进行增强、修改和定制,这正是Python语言灵活性的一个重要体现。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python Web 框架 Django 深度开发
Python Web 框架 Django 深度开发

本专题系统讲解 Python Django 框架的核心功能与进阶开发技巧,包括 Django 项目结构、数据库模型与迁移、视图与模板渲染、表单与认证管理、RESTful API 开发、Django 中间件与缓存优化、部署与性能调优。通过实战案例,帮助学习者掌握 使用 Django 快速构建功能全面的 Web 应用与全栈开发能力。

167

2026.02.04

python中print函数的用法
python中print函数的用法

python中print函数的语法是“print(value1, value2, ..., sep=' ', end=' ', file=sys.stdout, flush=False)”。本专题为大家提供print相关的文章、下载、课程内容,供大家免费下载体验。

193

2023.09.27

python print用法与作用
python print用法与作用

本专题整合了python print的用法、作用、函数功能相关内容,阅读专题下面的文章了解更多详细教程。

19

2026.02.03

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

891

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

32

2025.12.06

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

390

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2112

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

357

2023.08.31

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号