0

0

Python中装饰器怎么用 Python中装饰器使用指南

下次还敢

下次还敢

发布时间:2025-08-27 15:50:01

|

197人浏览过

|

来源于php中文网

原创

装饰器是Python中用于包装或修改函数、方法或类行为的高阶函数,无需修改原代码即可添加日志、计时、权限校验等横切关注点。其核心语法为@decorator_name,本质是将函数作为参数传入装饰器并返回新函数。使用functools.wraps可保留原函数元信息,避免调试困难。带参数的装饰器需多一层嵌套结构,如@log_level(level="DEBUG")。装饰器解决了代码重复和关注点分离问题,广泛应用于Web路由(@app.route)、权限控制(@login_required)、限流、缓存(@lru_cache)和重试机制等场景,提升代码模块化与可维护性。最佳实践包括单一职责、通用性设计、异常处理和避免滥用。

python中装饰器怎么用 python中装饰器使用指南

Python中的装饰器,说白了,就是一种特殊类型的函数,它的核心作用是用来包装或修改另一个函数、方法或类的行为,而不需要直接改动被包装对象的源代码。你可以把它想象成给一个函数穿上了一件外套,这件外套可以在函数执行前后添加额外的功能,比如日志记录、性能计时、权限检查等等。它让我们的代码变得更“干净”,更模块化,尤其是在处理那些跨越多个功能模块的“横切关注点”时,简直是神器。

要理解装饰器怎么用,我们得从最基础的语法和它背后的原理说起。

最常见的装饰器用法,就是直接在函数定义上方加上

@decorator_name
。这其实是Python提供的一个语法糖,它的本质是:

def original_function():
    pass

# 等价于
original_function = decorator_name(original_function)

所以,装饰器本身就是一个接受函数作为参数,并返回一个新函数(通常是内部定义的

wrapper
函数)的高阶函数。

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

我们来看一个最简单的例子,用来记录函数执行的日志:

import functools

def log_calls(func):
    # functools.wraps 是个好东西,它能把原函数的元信息(比如名字、文档字符串)复制到wrapper函数上
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"INFO: Calling function '{func.__name__}' with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"INFO: Function '{func.__name__}' finished, returned: {result}")
        return result
    return wrapper

@log_calls
def add(a, b):
    """计算两个数的和。"""
    print(f"Inside add: {a} + {b}")
    return a + b

@log_calls
def subtract(x, y):
    """计算两个数的差。"""
    print(f"Inside subtract: {x} - {y}")
    return x - y

# 使用被装饰的函数
sum_result = add(10, 5)
print(f"Sum result: {sum_result}\n")

diff_result = subtract(y=3, x=7)
print(f"Diff result: {diff_result}")

print(f"Name of decorated add function: {add.__name__}") # 输出 'add' 而不是 'wrapper',多亏了 @functools.wraps
print(f"Docstring of decorated add function: {add.__doc__}")

在这个例子里,

log_calls
就是我们的装饰器。它接收
add
subtract
函数作为参数,然后定义了一个内部函数
wrapper
wrapper
在调用原始函数前后打印日志,最后返回原始函数的结果。
log_calls
最终返回的就是这个
wrapper
函数。

如果你需要装饰器本身也接收参数,比如你想控制日志的级别,那就需要再多一层嵌套:

import functools

def log_level(level="INFO"):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] Calling '{func.__name__}'...")
            result = func(*args, **kwargs)
            print(f"[{level}] Finished '{func.__name__}'.")
            return result
        return wrapper
    return decorator

@log_level(level="DEBUG")
def process_data(data):
    """处理一些数据。"""
    print(f"Processing: {data}")
    return f"Processed: {data}"

@log_level() # 不传参数,使用默认的INFO级别
def fetch_config(key):
    """获取配置项。"""
    print(f"Fetching config for {key}")
    return {"key": key, "value": "some_value"}

process_data([1, 2, 3])
fetch_config("database_url")

这里

log_level
首先是一个工厂函数,它接收装饰器所需的参数(比如
level
),然后返回真正的装饰器函数
decorator
。这个
decorator
函数才是我们熟悉的那个接受函数作为参数并返回
wrapper
的结构。

为什么Python需要装饰器,它解决了什么痛点?

我个人觉得,装饰器最核心的价值,在于它提供了一种优雅的方式来处理“横切关注点”(Cross-Cutting Concerns)。什么是横切关注点?简单来说,就是那些散布在程序中多个模块,但又不属于任何一个模块核心业务逻辑的功能。最典型的就是日志、性能监控、事务管理、权限校验、缓存等。

想象一下,如果你有几十个甚至上百个函数都需要添加日志记录。如果没有装饰器,你可能得在每个函数的开头和结尾手动插入日志代码。这不仅会让代码变得臃肿、难以阅读,而且一旦日志逻辑需要修改(比如从打印到文件改成打印到数据库),你得修改所有这些地方。这简直是维护者的噩梦,也极大地增加了出错的风险。

无限画
无限画

千库网旗下AI绘画创作平台

下载

装饰器就像一个“即插即用”的插件系统。它把这些横切关注点从核心业务逻辑中剥离出来,让你的业务函数只专注于它应该做的事情。比如一个计算函数就只管计算,至于计算前后的日志、计时,那是装饰器的事情。这种分离关注点的设计,让代码更具可读性、可维护性和可扩展性。我记得刚开始接触Python时,看到

@
符号觉得很神奇,深入了解后才发现,这背后藏着一种非常强大的设计哲学。它把一些原本可能需要AOP(面向切面编程)框架才能实现的功能,用Python自身简洁的语法就搞定了,这不得不说是一种语言层面的智慧。

编写自定义装饰器时,有哪些常见的陷阱和最佳实践?

在自己动手写装饰器的时候,我踩过不少坑,也总结了一些经验。

一个最常见的陷阱就是忘记使用

functools.wraps
。如果你不加它,被装饰的函数会“丢失”它原本的元信息,比如它的名字(
__name__
)、文档字符串(
__doc__
)、参数签名等。在调试、生成文档或者依赖这些元信息的场景下,这会带来很大的麻烦。比如上面的
add.__name__
如果不加
@functools.wraps
,会输出
wrapper
而不是
add
。这听起来可能不严重,但在复杂的系统里,这会让调试变得像大海捞针。所以,把
@functools.wraps(func)
放在你的
wrapper
函数定义前,几乎应该成为一种习惯。

另一个我常犯的错误,是在

wrapper
函数里没有正确处理参数。如果你只是简单地写
def wrapper(arg1, arg2):
,那么你的装饰器就只能用于那些恰好有两个位置参数的函数。正确的做法是使用
*args
**kwargs
来捕获所有位置参数和关键字参数,这样你的装饰器才能通用地应用于各种参数签名的函数。

至于最佳实践,我觉得有几点特别重要:

  • 单一职责原则: 一个装饰器最好只做一件事情。比如一个装饰器只负责日志,另一个只负责计时。这样它们更易于理解、测试和组合。
  • 保持通用性: 尽量让你的装饰器能够应用于多种场景。这意味着你需要考虑参数化,就像我们上面
    log_level
    的例子那样。
  • 异常处理: 如果你的装饰器在执行前后添加了逻辑,考虑这些逻辑本身是否会抛出异常,以及如何处理被包装函数可能抛出的异常。有时,你可能需要在
    wrapper
    内部添加
    try...except
    块。
  • 清晰的文档: 为你的装饰器写好文档字符串,说明它的作用、参数和使用方式。这对于团队协作和未来的维护至关重要。
  • 避免过度使用: 装饰器虽然强大,但也不是万能药。在某些简单场景下,直接调用辅助函数可能比引入装饰器更清晰。

装饰器在实际项目中都有哪些高级应用场景?

装饰器这玩意儿,一旦你掌握了它,会发现它在实际项目中简直无处不在,而且能解决很多“头疼”的问题。

最直观的,就是各种Web框架里的应用。比如Flask或Django

@app.route('/path')
就是最经典的路由装饰器,它把URL路径和处理函数关联起来。还有像
@login_required
(检查用户是否登录)、
@permission_required('admin')
(检查用户权限)这些,它们在执行视图函数前,就完成了身份验证和权限校验,如果条件不满足,直接返回错误或重定向,根本不让业务逻辑有机会执行。这极大地简化了Web应用的开发。

再比如API限流。如果你有一个对外开放的API,为了防止恶意请求或者系统过载,你可能需要限制某个用户或某个IP在一定时间内的请求次数。这时,你可以写一个

@rate_limit(calls_per_minute=10)
这样的装饰器。它会在每次API调用前检查请求计数器,如果超限就直接拒绝。

缓存(Memoization)也是一个非常棒的应用场景。Python标准库里的

functools.lru_cache
就是一个很好的例子。当你有一个计算成本很高,但输入参数不变时输出结果也固定的函数,你就可以用
@functools.lru_cache()
来装饰它。这样,函数第一次执行时会计算结果并缓存起来,后续再用相同的参数调用时,直接从缓存中取结果,大大提升了性能。我用它优化过一些递归函数,效果立竿见影。

还有**重试

热门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 应用与全栈开发能力。

166

2026.02.04

Python Flask框架
Python Flask框架

本专题专注于 Python 轻量级 Web 框架 Flask 的学习与实战,内容涵盖路由与视图、模板渲染、表单处理、数据库集成、用户认证以及RESTful API 开发。通过博客系统、任务管理工具与微服务接口等项目实战,帮助学员掌握 Flask 在快速构建小型到中型 Web 应用中的核心技能。

106

2025.08.25

Python Flask Web框架与API开发
Python Flask Web框架与API开发

本专题系统介绍 Python Flask Web框架的基础与进阶应用,包括Flask路由、请求与响应、模板渲染、表单处理、安全性加固、数据库集成(SQLAlchemy)、以及使用Flask构建 RESTful API 服务。通过多个实战项目,帮助学习者掌握使用 Flask 开发高效、可扩展的 Web 应用与 API。

81

2025.12.15

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

651

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1228

2024.03.22

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号