Python装饰器如何扩展函数功能?
本篇文章向大家介绍《Python装饰器如何扩展函数功能?》,主要包括,具有一定的参考价值,需要的朋友可以参考一下。
装饰器是一个接收函数并返回新函数的高阶函数,用于在不修改原函数代码的情况下添加额外功能;2. 实现装饰器需定义外层函数接收原函数,内层wrapper函数封装原函数并添加逻辑,最后返回wrapper;3. 使用@decorator语法糖可简洁地应用装饰器,等价于func = decorator(func);4. wrapper函数应使用*args和**kwargs接收任意参数,以支持带参数的原函数;5. 为保留原函数的\_\_name\_\_、\_\_doc\_\_等元信息,应使用functools.wraps装饰wrapper;6. 带参数的装饰器需再增加一层函数嵌套,外层接收装饰器参数,中间层为真正的装饰器,内层为wrapper。装饰器通过函数嵌套和闭包机制,实现了对函数行为的动态增强,同时保持代码的清晰与复用性。

Python的装饰器,说白了,就是一种非常优雅且强大的语法糖,它允许你在不修改原始函数代码的前提下,给函数动态地添加一些额外的功能或者修改它的行为。你可以把它想象成给函数穿上一件“外套”,这件外套能做很多事情,比如日志记录、性能监控、权限校验等等,而函数本身对此毫不知情,或者说,它根本不需要关心这些。
解决方案
要用装饰器给函数添加额外功能,核心思路是创建一个“高阶函数”。这个高阶函数会接收一个函数作为参数,然后返回一个新的函数。这个新的函数通常是一个“闭包”,它在内部调用了原始函数,并在调用前后(或者根据需要)执行一些额外的逻辑。Python通过@符号提供了一种非常方便的语法糖来使用装饰器,让代码看起来更简洁直观。
举个例子,假设我们想给一个函数添加一个日志功能,每次函数执行前都打印一条信息,执行后也打印一条。
def log_decorator(func):
"""这是一个简单的日志装饰器"""
def wrapper(*args, **kwargs):
print(f"--- 函数 '{func.__name__}' 即将执行 ---")
result = func(*args, **kwargs) # 调用原始函数
print(f"--- 函数 '{func.__name__}' 执行完毕 ---")
return result
return wrapper
@log_decorator
def greet(name):
"""一个简单的问候函数"""
print(f"你好,{name}!")
return f"Hello, {name}!"
@log_decorator
def calculate_sum(a, b):
"""计算两个数的和"""
print(f"正在计算 {a} + {b}...")
return a + b
# 使用被装饰的函数
greet("小明")
print("-" * 20)
total = calculate_sum(10, 20)
print(f"计算结果是: {total}")在这个例子里,log_decorator就是我们的装饰器。它接收greet或calculate_sum函数,然后返回一个wrapper函数。当我们调用greet("小明")时,实际上执行的是log_decorator返回的那个wrapper函数,这个wrapper函数在内部调用了真正的greet函数,并在前后添加了日志打印。
装饰器究竟是个什么“东西”?
说实话,刚接触装饰器的时候,我个人觉得它有点绕,尤其是那个嵌套的函数结构。但当你理解了它的本质,就会发现它其实是Python里“函数是第一类对象”这个概念的极致体现。简单来说,装饰器就是一个能接受函数作为输入,并返回一个新函数的函数。
用更具体一点的说法,当你写下@some_decorator在函数定义上方时,这行代码等价于:
def original_function():
pass
# original_function = some_decorator(original_function)
# 语法糖的背后就是这样它把original_function作为参数传给了some_decorator,然后把some_decorator的返回值(通常是另一个函数,也就是我们上面例子里的wrapper)重新赋值给了original_function这个名字。所以,之后你再调用original_function()时,实际上调用的是被装饰器“加工”过的新函数。
这种设计哲学,我个人觉得非常棒,它让代码的关注点分离变得更容易。比如,你的核心业务逻辑可以保持干净,而那些通用的、横切面的功能(像日志、缓存、权限等)则可以通过装饰器优雅地注入进来,互不干扰。
写一个最简单的装饰器,我该从何入手?
要写一个最简单的装饰器,你得先理解那个“两层嵌套”的结构。这确实是初学者的一道坎,但一旦跨过去,就豁然开朗了。
步骤拆解:
- 定义外层函数: 这个函数就是你的装饰器本身,它接受一个参数,这个参数就是你要装饰的那个函数(我们通常称之为
func或original_func)。 - 定义内层函数(wrapper): 在外层函数内部,你需要定义另一个函数,我们习惯叫它
wrapper。这个wrapper函数才是真正用来替代原始函数的。它会负责调用原始函数,并在调用前后加入你想要实现的额外逻辑。 - 返回内层函数: 外层函数最后需要返回这个
wrapper函数。
我们来写一个最最简单的,比如一个统计函数执行时间的装饰器:
import time
def timer_decorator(func):
"""一个简单的计时装饰器"""
def wrapper(*args, **kwargs): # 注意这里,需要接受任意参数
start_time = time.time()
result = func(*args, **kwargs) # 调用原始函数
end_time = time.time()
print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timer_decorator
def long_running_task():
"""一个模拟耗时操作的函数"""
print("开始执行耗时任务...")
time.sleep(2) # 模拟IO或计算密集型操作
print("耗时任务完成。")
return "任务结果"
@timer_decorator
def greet_person(name, age):
print(f"你好,{name}!你今年{age}岁了。")
time.sleep(0.5)
return "问候完毕"
# 调用被装饰的函数
task_result = long_running_task()
print(f"任务最终结果: {task_result}")
print("-" * 20)
greet_person("张三", 30)这里有个小细节,你可能注意到了wrapper(*args, **kwargs)。这是为了确保你的装饰器能够处理任何带有参数(位置参数和关键字参数)的函数。如果没有它们,你的装饰器就只能装饰那些不带参数的函数了,这显然不符合实际需求。
还有一个常见的问题,就是被装饰后的函数,它的__name__、__doc__等元信息会变成wrapper函数的元信息,而不是原始函数的。这在调试或者一些框架反射时会造成困扰。解决方案也很简单,Python标准库里提供了functools.wraps这个装饰器,它可以帮助我们把原始函数的元信息“拷贝”到wrapper函数上。
import time
from functools import wraps # 导入wraps
def timer_decorator_with_wraps(func):
"""一个带functools.wraps的计时装饰器"""
@wraps(func) # 使用wraps来保留原始函数的元信息
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 '{func.__name__}' 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timer_decorator_with_wraps
def another_task():
"""这是另一个任务的文档字符串"""
time.sleep(1)
return "另一个任务完成"
print(f"函数名: {another_task.__name__}") # 输出会是 'another_task' 而不是 'wrapper'
print(f"文档: {another_task.__doc__}")这下,another_task的__name__和__doc__就正确了,这在实际开发中非常有用。
装饰器能处理带参数的函数吗?
当然可以,而且在实际应用中,绝大多数被装饰的函数都是带参数的。前面在写timer_decorator的时候,我已经悄悄地展示了这一点:wrapper(*args, **kwargs)就是处理带参数函数的关键。
*args会收集所有传递给wrapper的位置参数,并将它们打包成一个元组。
**kwargs会收集所有传递给wrapper的关键字参数,并将它们打包成一个字典。
然后,你只需要把这些收集到的参数原封不动地传递给原始函数func(*args, **kwargs),这样无论原始函数需要什么参数,都能正确地接收到。
我们再来看一个稍微复杂一点的例子,比如一个权限检查的装饰器,它需要知道当前用户是否有权限访问某个函数,而这个函数本身可能也需要参数:
from functools import wraps
# 模拟一个用户权限列表
AUTHORIZED_USERS = {"admin", "editor"}
def permission_required(allowed_roles):
"""
一个需要权限的装饰器,装饰器本身也带参数。
它需要一个参数来指定哪些角色被允许。
"""
def decorator(func): # 这是真正的装饰器函数,它接收被装饰的函数
@wraps(func)
def wrapper(current_user, *args, **kwargs): # wrapper需要接收用户参数以及原始函数的其他参数
if current_user in allowed_roles:
print(f"用户 '{current_user}' 权限通过,执行 '{func.__name__}'。")
return func(current_user, *args, **kwargs) # 传递所有参数给原始函数
else:
print(f"用户 '{current_user}' 没有权限执行 '{func.__name__}'。所需权限: {allowed_roles}")
return None # 或者抛出异常
return wrapper
return decorator
@permission_required(allowed_roles=AUTHORIZED_USERS)
def view_sensitive_data(user, data_id):
"""查看敏感数据的功能"""
print(f"用户 '{user}' 正在查看数据ID: {data_id}")
return f"敏感数据 {data_id} 的详细内容。"
@permission_required(allowed_roles={"admin"})
def delete_user_account(user, user_to_delete):
"""删除用户账户的功能"""
print(f"用户 '{user}' 正在尝试删除用户: {user_to_delete}")
return f"用户 '{user_to_delete}' 已被删除。"
# 测试
print("--- 尝试查看敏感数据 ---")
result1 = view_sensitive_data("admin", "ABC123")
print(f"结果: {result1}\n")
result2 = view_sensitive_data("guest", "XYZ789")
print(f"结果: {result2}\n")
print("--- 尝试删除用户账户 ---")
result3 = delete_user_account("admin", "john_doe")
print(f"结果: {result3}\n")
result4 = delete_user_account("editor", "jane_doe")
print(f"结果: {result4}\n")这个例子里,permission_required本身也是一个函数,它接受allowed_roles参数,然后返回一个真正的装饰器函数decorator。这个decorator再接收func,并返回wrapper。这就是所谓的“带参数的装饰器”,它比前面不带参数的装饰器多了一层嵌套,但核心思想没变。wrapper函数依然通过*args和**kwargs完美地处理了原始函数view_sensitive_data和delete_user_account可能带有的其他参数。
所以,无论是原始函数有没有参数,装饰器都能灵活地处理,关键就在于wrapper函数签名中的*args和**kwargs,以及你如何把它们传给原始函数。
文中关于高阶函数,Python装饰器,@语法糖,函数功能扩展,wrapper函数的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《Python装饰器如何扩展函数功能?》文章吧,也可关注golang学习网公众号了解相关技术文章。
Python批量发邮件技巧分享
- 上一篇
- Python批量发邮件技巧分享
- 下一篇
- Golangsort自定义排序实现教程
-
- 文章 · python教程 | 6小时前 |
- Python语言入门与基础解析
- 296浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- PyMongo导入CSV:类型转换技巧详解
- 351浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- Python列表优势与实用技巧
- 157浏览 收藏
-
- 文章 · python教程 | 6小时前 |
- Pandas修改首行数据技巧分享
- 485浏览 收藏
-
- 文章 · python教程 | 8小时前 |
- Python列表创建技巧全解析
- 283浏览 收藏
-
- 文章 · python教程 | 8小时前 |
- Python计算文件实际占用空间技巧
- 349浏览 收藏
-
- 文章 · python教程 | 9小时前 |
- OpenCV中OCR技术应用详解
- 204浏览 收藏
-
- 文章 · python教程 | 10小时前 |
- Pandas读取Django表格:协议关键作用
- 401浏览 收藏
-
- 文章 · python教程 | 10小时前 | 身份验证 断点续传 requests库 PythonAPI下载 urllib库
- Python调用API下载文件方法
- 227浏览 收藏
-
- 文章 · python教程 | 10小时前 |
- Windows7安装RtMidi失败解决办法
- 400浏览 收藏
-
- 文章 · python教程 | 11小时前 |
- Python异步任务优化技巧分享
- 327浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3182次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3393次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3424次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4528次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3802次使用
-
- Flask框架安装技巧:让你的开发更高效
- 2024-01-03 501浏览
-
- Django框架中的并发处理技巧
- 2024-01-22 501浏览
-
- 提升Python包下载速度的方法——正确配置pip的国内源
- 2024-01-17 501浏览
-
- Python与C++:哪个编程语言更适合初学者?
- 2024-03-25 501浏览
-
- 品牌建设技巧
- 2024-04-06 501浏览

