Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
python如何实现一个装饰器_python装饰器原理与实现方法详解_创想鸟

python如何实现一个装饰器_python装饰器原理与实现方法详解

Python装饰器利用函数为一等公民和闭包特性,通过@语法为函数添加功能而不修改其代码。如log_calls装饰器可记录函数调用日志,核心是外部函数返回嵌套的wrapper函数,wrapper保留对原函数的引用并扩展行为。functools.wraps确保被装饰函数的元信息不变。带参数的装饰器需多一层函数嵌套,形成“装饰器工厂”,如timer(unit)返回真正的装饰器。类也可作为装饰器,通过实现__call__方法,在实例中保存状态,适用于需维护调用次数或共享资源的场景,如CallCounter统计函数调用次数。

python如何实现一个装饰器_python装饰器原理与实现方法详解

Python装饰器,说白了,就是一种特殊的函数,它的主要工作是去“包裹”或者说“包装”另一个函数,给这个被包装的函数增加额外的功能,但又不需要我们去直接修改被包装函数的源代码。这听起来有点像给一个礼物盒外面再套一层包装纸,里面的礼物(原函数)还是那个礼物,但外面的包装纸(装饰器)给它增添了新的“仪式感”或者说“功能”。它的核心原理,其实就是利用了Python中函数是“一等公民”的特性,以及闭包(closure)的概念,通过

@

这个语法糖,让代码变得非常简洁和易读。

解决方案

要实现一个装饰器,我们通常会定义一个外部函数,这个外部函数接收一个函数作为参数(也就是我们要装饰的那个函数)。在外部函数内部,我们再定义一个嵌套函数(通常命名为

wrapper

),这个

wrapper

函数才是真正执行额外逻辑的地方,它会调用原始函数,并在调用前后做一些事情。最后,外部函数会返回这个

wrapper

函数。

举个最常见的例子,我们想给一个函数加上日志功能,记录它被调用的时间和参数:

import timeimport functoolsdef log_calls(func):    """    一个简单的日志装饰器,记录函数调用。    """    @functools.wraps(func) # 这一行很重要,它保留了原函数的元信息    def wrapper(*args, **kwargs):        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 调用函数: {func.__name__},参数: {args}, {kwargs}")        result = func(*args, **kwargs)        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 函数 {func.__name__} 执行完毕,返回: {result}")        return result    return wrapper@log_callsdef add(a, b):    """计算两个数的和"""    time.sleep(0.1) # 模拟耗时操作    return a + b@log_callsdef greet(name, greeting="Hello"):    """向指定名字的人打招呼"""    return f"{greeting}, {name}!"# 调用被装饰的函数print(f"结果: {add(10, 20)}")print(f"结果: {greet('Alice', greeting='Hi')}")

这里,

log_calls

就是我们的装饰器。当我们在

add

函数上方写上

@log_calls

时,Python解释器实际上做了这样的事情:

add = log_calls(add)

。也就是说,

add

这个变量现在指向的不再是原来的

add

函数,而是

log_calls

函数返回的那个

wrapper

函数。当调用

add(10, 20)

时,实际上是调用了

wrapper(10, 20)

,

wrapper

内部再调用原始的

add

函数。

functools.wraps

的使用是为了让装饰后的函数仍然保持原函数的名称、文档字符串等元信息,这在调试和使用帮助文档时非常有用,不然你看到的函数名就都是

wrapper

了,那可就太让人困惑了。

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

Python装饰器背后的魔法:闭包与函数作为一等公民

在我看来,要真正理解装饰器,就得先搞明白Python里“函数是第一类对象(First-Class Citizen)”这个概念,以及“闭包(Closure)”是什么。这不光是装饰器的基石,也是Python很多高级特性的核心。

函数作为一等公民,意味着函数在Python里和整数、字符串这些数据类型没什么两样。你可以:

把函数赋值给变量:

my_func = add

把函数作为参数传给另一个函数:

map(str, [1, 2, 3])

把函数作为另一个函数的返回值:这正是装饰器里外部函数返回

wrapper

的关键。把函数存储在数据结构里(比如列表或字典)。

而闭包,则是在一个函数内部定义了另一个函数,并且内部函数引用了外部函数的局部变量,当外部函数执行完毕并返回内部函数时,即使外部函数的执行环境已经销毁,内部函数仍然能够“记住”并访问外部函数的那些局部变量。

在我们的

log_calls

例子里:

log_calls

是外部函数。

func

(也就是被装饰的

add

或

greet

)是

log_calls

的局部变量。

wrapper

是内部函数,它引用了外部函数的局部变量

func

。当

log_calls

执行完毕并返回

wrapper

时,

wrapper

就形成了一个闭包,它“捕获”了

func

这个变量。所以,即使

log_calls

已经执行完了,

wrapper

在被调用时依然知道它应该去调用哪个原始函数。

这种机制非常强大,它允许我们在不修改原函数代码的前提下,对其行为进行扩展。这在很多场景下都极其有用,比如权限验证、缓存、性能监控、事务管理等等,都是典型的“横切关注点”,用装饰器来处理简直是天作之合。

如何编写带参数的装饰器?

有时候,我们希望装饰器本身也能接受一些配置参数,比如一个日志装饰器,我们可能想指定日志级别,或者一个权限装饰器,我们想指定需要的角色。这时,我们的装饰器就需要变成一个“装饰器工厂”,也就是说,一个函数,它接收参数,然后返回一个真正的装饰器。

这个模式会多一层嵌套,看起来可能会有点绕,但理解了前面闭包的概念,这也就水到渠成了。

import timeimport functoolsdef timer(unit="seconds"):    """    一个带参数的计时装饰器,可以指定时间单位。    unit: 'seconds', 'milliseconds', 'microseconds'    """    def decorator(func): # 这才是真正的装饰器        @functools.wraps(func)        def wrapper(*args, **kwargs):            start_time = time.perf_counter()            result = func(*args, **kwargs)            end_time = time.perf_counter()            duration = end_time - start_time            if unit == "milliseconds":                duration *= 1000                unit_str = "ms"            elif unit == "microseconds":                duration *= 1_000_000                unit_str = "μs"            else:                unit_str = "s"            print(f"函数 {func.__name__} 执行耗时: {duration:.4f} {unit_str}")            return result        return wrapper    return decorator@timer(unit="milliseconds") # 这里传递了参数def complex_calculation(n):    """模拟一个复杂的计算"""    total = 0    for i in range(n):        total += i * i    time.sleep(0.05) # 额外模拟一点IO耗时    return total@timer() # 不传参数时,使用默认单位def simple_task():    """一个简单的任务"""    time.sleep(0.02)    return "Task Done"print(f"计算结果: {complex_calculation(100000)}")print(f"任务状态: {simple_task()}")

这里,

timer

函数就是那个“装饰器工厂”。它接收

unit

参数,然后返回

decorator

函数。

decorator

函数才是我们熟悉的那个接收函数作为参数并返回

wrapper

的结构。当写

@timer(unit="milliseconds")

时,Python解释器首先调用

timer("milliseconds")

,这会返回

decorator

函数。然后,这个返回的

decorator

函数再被用来装饰

complex_calculation

,等价于

complex_calculation = decorator(complex_calculation)

。这样,

unit

这个参数就被

decorator

和

wrapper

形成的闭包“捕获”了,可以在

wrapper

内部使用。

深入探索:类装饰器与更灵活的状态管理

除了函数装饰器,Python还允许我们使用类来作为装饰器。类装饰器在某些场景下,比如需要维护状态、或者需要更复杂的初始化逻辑时,会显得更加直观和强大。

一个类要作为装饰器,最核心的一点是它需要实现

__call__

方法。这样,类的实例就可以像函数一样被调用。当类被用作装饰器时,

@ClassName

实际上是创建了

ClassName

的一个实例,然后用这个实例来替换被装饰的函数。

import timeimport functoolsclass CallCounter:    """    一个类装饰器,用于统计函数被调用的次数。    """    def __init__(self, func):        # 初始化时,接收被装饰的函数        functools.update_wrapper(self, func) # 同样保留原函数元信息        self.func = func        self.count = 0 # 维护调用次数的状态    def __call__(self, *args, **kwargs):        # 当被装饰的函数被调用时,实际上是调用了__call__方法        self.count += 1        print(f"函数 {self.func.__name__} 已被调用 {self.count} 次。")        return self.func(*args, **kwargs)@CallCounterdef calculate_sum(a, b):    """计算和"""    return a + b@CallCounterdef say_hello(name):    """打招呼"""    return f"Hello, {name}!"# 调用被装饰的函数print(calculate_sum(1, 2))print(calculate_sum(3, 4))print(say_hello("World"))print(calculate_sum(5, 6))

这里,

CallCounter

类被用作装饰器。当

@CallCounter

作用于

calculate_sum

时,Python解释器会执行

calculate_sum = CallCounter(calculate_sum)

。这意味着

calculate_sum

现在不再是原来的函数,而是

CallCounter

类的一个实例。当后续调用

calculate_sum(1, 2)

时,实际上是调用了这个实例的

__call__

方法,从而实现了计数和原始函数调用的逻辑。

类装饰器特别适合需要内部状态或者需要在多个被装饰函数之间共享某些配置或资源的场景。比如,一个数据库连接池的装饰器,或者一个复杂的缓存机制,用类来实现可能会让代码结构更清晰,状态管理也更集中。当然,这并不是说函数装饰器就不能实现有状态的,只是类提供了一种更面向对象的封装方式。选择哪种方式,很多时候取决于具体的需求和个人偏好,但了解它们的原理和适用场景,总能帮助我们做出更明智的决策。

以上就是python如何实现一个装饰器_python装饰器原理与实现方法详解的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1371905.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
python中global关键字怎么使用_Python global关键字修改全局变量
上一篇 2025年12月14日 11:51:43
FastAPI大文件高效下载实践:告别内存溢出,巧用FileResponse
下一篇 2025年12月14日 11:52:00

相关推荐

  • 哔哩哔哩怎么设置点赞和投币记录为私密_哔哩哔哩点赞投币隐私设置

    1、进入哔哩哔哩App个人主页,点击头像进入个人空间,通过右上角菜单进入设置;2、开启“隐藏我的点赞”功能,防止他人查看点赞记录;3、在隐私权限设置中关闭“展示投币动态”,限制投币行为的公开显示;4、手动检查并删除或隐藏历史动态中的互动记录,确保过往点赞与投币不被他人可见。 如果您希望在使用哔哩哔哩…

    2026年9月21日
    100
  • 蝴蝶号内容创作不露脸的五大绝技与执行方法 | 快速提升曝光率的实用操作流程

    不露脸也能玩转蝴蝶号内容创作,关键在于将焦点从个人形象转移到内容本身与观众体验上,通过声音叙事、动态文字、手部特写、数据可视化和场景搭建五大核心策略构建吸引力,结合高质量音画配合、精准的受众定位、稳定更新与算法互动,提升曝光率;同时规避素材版权、声音质量与画面单调等技术挑战,善用免费或付费正版素材、…

    2026年9月21日
    100
  • 如何为VSCode设置最小化到系统托盘?

    VSCode不支持内置最小化到系统托盘功能,可通过第三方工具实现:Windows推荐使用RBTray或AutoHotkey脚本,Linux可借助AppIndicator扩展,macOS则依赖Dock最小化及辅助工具视觉隐藏。 VSCode 本身不提供内置的“最小化到系统托盘”功能,但可以通过一些方法…

    2026年9月21日
    000
  • 怎样在iPhone情侣模式中设置情侣专属表情?个性化聊天的技巧

    怎样在iPhone情侣模式中设置情侣专属表情?个性化聊天的技巧怎样在iPhone情侣模式中设置情侣专属表情?个性化聊天的技巧怎样在iPhone情侣模式中设置情侣专属表情?个性化聊天的技巧怎样在iPhone情侣模式中设置情侣专属表情?个性化聊天的技巧

    通过Memoji、第三方贴纸应用和iOS 16+抠图功能,可为情侣打造专属表情包;结合自定义聊天背景、语音消息、共享相册等方式,既能提升聊天趣味性,又能保持沟通效率,增强情感连接。 在iPhone上设置情侣专属表情,与其说是开启一个内置的“情侣模式”,不如说是巧妙利用iOS系统和第三方应用提供的各种…

    2026年9月21日 • 用户投稿
    100
  • PHP 数组值比较与嵌套数组过滤教程

    本教程详细讲解如何在 PHP 中比较一个简单数组与一个复杂嵌套数组,并根据特定条件(如文件名匹配)过滤嵌套数组中的所有相关子数组。我们将通过识别非匹配项的索引,然后从所有子数组中移除这些项并重新索引,实现精确的数据筛选。 问题背景 在 php 开发中,我们经常会遇到需要处理结构复杂的数组数据。例如,…

    2026年9月21日
    100
  • 如何使用XGBoost训练AI大模型?优化机器学习模型的步骤

    XGBoost并非用于训练GPT类大模型,而是擅长处理结构化数据的高效梯度提升算法,其优势在于速度快、准确性高、支持并行计算、内置正则化与缺失值处理,适用于表格数据建模;通过分阶段超参数调优(如学习率、树深度、采样策略)、结合贝叶斯优化与交叉验证,并配合特征工程、数据预处理和集成学习等关键步骤,可显…

    2026年9月21日
    000
  • 美图秀秀导出视频卡住 美图视频保存失败修复方案

    导出视频卡住或保存失败,通常和设备性能、软件状态或操作方式有关。直接强制退出再尝试是很多人会做的,但更有效的是先排查具体原因。 检查设备资源与软件状态 导出视频是个高负载任务,容易因资源不足中断。 关闭后台应用:尤其是浏览器、游戏或其他大型程序,释放内存和处理器资源。 确认存储空间:确保手机或电脑有…

    2026年9月21日
    000
  • 苹果为何把Apple ID改名为Apple Account

    苹果公司宣布将“Apple ID”更名为“Apple Account”,这一变化迅速引发热议。虽然只是名称上的调整,但其背后蕴含着深远的战略考量。 体现服务边界的扩展 随着苹果生态系统日益庞大,原有的“ID”一词已难以全面涵盖用户通过该账户所使用的广泛功能。如今,这一个账户不仅用于设备激活和App …

    2026年9月21日
    100
  • VSCode怎么运行全部代码_VSCode批量执行代码教程

    在VSCode里“运行全部代码”或“批量执行代码”,其实很少是一个单一的、所有语言通用的按钮。它更多的是指根据你项目的具体需求,通过配置任务(Tasks)、使用集成终端(Integrated Terminal)配合脚本,或者利用特定语言的运行/调试配置(Launch Configurations)来…

    2026年9月21日
    100
  • VSCode怎么新建ipynb文件_VSCode创建和编辑Jupyter笔记本文件教程

    答案:在VSCode中运行Jupyter笔记本需准备Python环境、安装Python扩展并确保安装ipykernel;通过命令面板或文件菜单新建笔记本,编辑时可添加代码或Markdown单元格,运行代码后结果实时显示;通过右上角内核选择器切换Python环境,推荐为不同项目配置独立虚拟环境以避免依…

    2026年9月21日
    200
  • 夸克Ai搜索如何设置默认_夸克Ai搜索默认引擎更改

    首先在夸克APP中将默认搜索引擎设为AI引擎,再开启相关AI功能开关以启用AI搜索服务。具体步骤:1、打开夸克APP,点击右下角菜单进入设置;2、选择“通用”选项,点击“搜索引擎”;3、选择“AI引擎”或“夸克AI搜索”作为默认服务;4、返回主界面测试搜索关键词,确认AI结果是否展示;5、进入“AI…

    2026年9月21日
    400
  • iPhone 17 Pro如何关闭后台应用刷新

    关闭iPhone后台应用刷新可省电省流量,进入设置→通用→后台App刷新,关闭顶部总开关或单独关闭特定App,还能提升系统流畅度。 虽然目前还没有iPhone 17 Pro,但关闭后台应用刷新的方法在所有iPhone上都是一样的。你可以通过设置里的“通用”选项来管理这个功能,既能省电也能减少数据使用…

    2026年9月21日
    100
  • mysql如何实现后台管理系统

    答案:基于MySQL的%ignore_a_1%需设计用户、权限、日志等表结构,通过后端语言实现安全的CRUD接口与JWT认证,前端展示数据并控制权限,确保系统安全稳定。 实现一个基于 MySQL 的后台管理系统,核心是构建一个安全、稳定、可扩展的系统架构,将数据库作为数据存储层,配合后端语言和前端界…

    2026年9月21日
    000
  • 115网盘资源查找入口_115网盘资源快速链接通道

    115网盘资源查找入口为http://www.115.com/,支持多平台访问、高效媒体管理及安全存储,提供网页端与客户端多种使用方式。 115网盘资源查找入口在哪里?这是不少网友都关注的,接下来由PHP小编为大家带来115网盘资源快速链接通道,感兴趣的网友一起随小编来瞧瞧吧! http://www…

    2026年9月21日
    000
  • OPPO A2 Pro充电提示音太响怎么关 OPPO A2 Pro系统音量管理

    关闭充电提示音最简单:进入设置→声音与振动→系统反馈→关闭充电提示音;若通过Breeno设置了自动指令,需在小布指令中删除相关规则;也可调低系统反馈中的充电提示音量以降低响度。 OPPO A2 Pro充电提示音太响,可以通过关闭系统中的充电提示音功能来解决。这个声音属于系统反馈音效,并非应用通知,所…

    2026年9月21日
    400
  • 压力测试(Benchmark)Swoole服务的工具与方法

    进行swoole服务的压力测试是为了确保服务在高负载下稳定运行。1. 选择工具:apache jmeter、wrk、locust。2. 使用方法:jmeter通过脚本配置,wrk通过命令行,locust通过python脚本。3. 注意事项:环境隔离、数据监控、脚本设计。4. 优化点:内存泄漏、连接池…

    2026年9月21日
    000
  • iPhone 12 Pro Max如何启用防水提示

    iPhone 12 Pro Max 具备 IP68 级防水,依赖密封设计无需启用;进水时会提示“闪电符号”,需晾干并避免使用吹风机,防水性能随时间可能下降。 iPhone 12 Pro Max 没有需要“启用”的防水提示功能。它的防溅、抗水和防尘能力是出厂时的硬件设计特性,无法通过设置开关来开启或关…

    2026年9月21日
    100
  • Linux目录结构与Windows目录结构对比

    Linux采用单一树状结构,所有文件系统挂载于根目录/下,如/home、/etc;Windows以C:\、D:\等独立盘符划分,无统一根节点。2. Linux将配置集中于/etc,用户数据存于/home,系统文件在/bin、/usr等,配置明文可编辑;Windows程序装在Program Files…

    用户投稿 2026年9月21日
    100
  • 利用蝴蝶号搭建多账号无人直播系统的完整方案

    利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案利用蝴蝶号搭建多账号无人直播系统的完整方案

    搭建多账号无人直播系统并非一键操作,而是通过“蝴蝶号”实现自动化流程。首先,“蝴蝶号”负责多账号的生命周期管理,包括登录、状态维护、ip代理分配和设备指纹模拟;其次,内容调度系统决定直播内容及播放时间,可为预录视频或动态生成流;再次,推流引擎将内容实时推送至平台,推荐使用ffmpeg结合python…

    2026年9月21日 • 用户投稿
    100
  • 大润发优鲜如何邀请新用户

    你可以通过生成个人专属的邀请链接来参与大润发优鲜的“邀请有礼”活动。在App内找到相关入口后,系统会为你生成唯一的邀请链接。将这个链接通过微信、QQ、短信或其他社交渠道分享给朋友或家人,一旦对方点击链接并完成大润发优鲜App的下载与注册,你就能获得平台发放的奖励,例如购物优惠券或积分,而新用户通常也…

    2026年9月21日
    000

发表回复

登录后才能评论
关注微信