【Python3基础】08-高阶函数、闭包与装饰器
▒ 目录 ▒ 导读本篇知识路线1️⃣ 匿名函数匿名函数的定义和使用匿名函数的优点2️⃣ 闭包闭包介绍闭包的注意事项闭包的作用3️⃣ 装饰器写个装饰器装饰器语法糖4️⃣ 装饰器适合处理什么 文章小结 导读当函数能够被传递、保存和返回Python 程序就有了更灵活的组合方式。本篇从匿名函数开始逐步进入闭包和装饰器。三者看似独立实际都建立在“函数也是对象”这一基础上。闭包让内部函数保留外部状态装饰器则在不改动原函数主体的前提下为函数增加通用行为。本篇知识路线外部函数 ├─ 局部状态 └─ 内部函数 ──► 作为结果返回 │ └─ 闭包继续引用外部状态 原函数 ──► 装饰器包装 ──► 增强后的函数1️⃣ 匿名函数前面我们学习了函数Python 不仅仅满足有对函数参数进行各种装饰的语法糖而且还有了匿名函数的高逼格姿势今天我们就来学习一下匿名函数 lambda。匿名函数的定义和使用在前面的函数知识学习后我们知道在 python 中用【def 函数名参数】的方式定义函数即deffunction_name(parameters):pass这样的函数拥有函数名调用函数只需要function_name(input_parameter)我们把 def 定义的函数叫作“有名函数”那什么是匿名函数呢顾名思义这类函数没有显示地定义函数名。匿名函数不需要显示地定义函数名使用【lambda 参数 表达式】的方式即lambda [arg1 [,arg2, ... argN]] : expression也就是说lambda 用来表示匿名函数可以传入多个参数但只能有一个表达式。比方说我要写一个函数用于两个数相乘func lambda x, y: x*y可以看到上面我们把匿名函数对象赋给一个变量只要直接调用该对象就可以使用匿名函数func(2, 3) # 结果为 6当然匿名函数和有名函数一样都是函数匿名函数具有有名函数的一切特点。匿名函数的优点以上对匿名函数作了解释也举了一些例子用以说明。那么匿名函数的优点是什么呢不用取名称因为给函数取名是比较头疼的一件事特别是函数比较多的时候。可以直接在使用的地方定义如果需要修改直接找到修改即可方便以后代码的维护工作。语法结构简单不用使用def 函数名(参数名):这种方式定义直接使用lambda 参数:返回值定义即可。2️⃣ 闭包闭包是函数式编程的一个重要的语法结构简单来说闭包就是一个函数定义中引用了函数外定义的变量并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。闭包并不只是一个 python 中的概念在函数式编程语言中应用较为广泛。理解 python 中的闭包一方面是能够正确的使用闭包另一方面可以好好体会和思考闭包的设计思想。闭包介绍不同编程语言实现闭包的方式是不同的python 中闭包从表现形式上看如果在一个内部函数里对在外部作用域但不是在全局作用域的变量进行引用那么内部函数就被认为是闭包(closure)。defouter(x):definner(y):returnxyreturninner如上代码所示inner(y)就是这个内部函数对在外部作用域但不是在全局作用域的变量进行引用x 就是被引用的变量x 在外部作用域 outer 里面但不在全局作用域里则这个内部函数 inner 就是一个闭包。在函数 outer 中定义了一个 inner 函数inner 函数访问外部函数 outer 的参数变量并且把 inner 函数作为返回值返回给 outer 函数。当我们调用函数的时候必须调用外部函数因为内部函数对我们来说是不可见的。aouter(150)print(a(100))上面的代码中语句aouter(150)outer 返回一个函数inner赋值给 a此时 a 就是函数 inner。然后语句print(a(100))就是调用 inner 函数并传入一个参数 100此时a(100)的值为 250。一般情况下在我们认知当中如果一个函数结束函数的内部所有东西都会释放掉还给内存局部变量都会消失。但是闭包是一种特殊情况如果外函数在结束的时候发现有自己的局部变量将来会在内部函数中用到就把这个临时变量绑定给了内部函数然后自己再结束。闭包的注意事项闭包无法修改外部函数的局部变量。defouter():x200definner():x250print(inner x:,x)print(outer x:,x)# 调用 inner 函数之前inner()print(outer x:,x)# 调用 inner 函数之后outer()运行结果outer x: 200 inner x: 250 outer x: 200从结果上我们可以得知 x 的值并没有被内部函数修改在调用 inner 函数前后 x 的值没有发生变化。闭包无法直接访问外部函数的局部变量。defouter():x250definner():# 上面一行的 x 相对 inner 函数来说是函数外的局部变量非全局变量xxreturnxreturninner outer()()运行结果报异常UnboundLocalError: local variable x referenced before assignment但是我们可以间接地通过容器类型来解决因为容器类型不是存放在栈空间的inner 函数可以访问到。defouter():x[250]definner():x[0]x[0]returnx[0]returninnerprint(outer()())# 500同样我们也可以通过 nonlocal 关键字来解决该语句显式的指定 x 不是闭包的局部变量。defouter():x250definner():nonlocalx# 把 x 声明为非局部变量xxreturnxreturninnerprint(outer()())循环中不包含域的概念。还有一个容易产生错误的事例也经常被人在介绍 python 闭包时提起我一直都没觉得这个错误和闭包有什么太大的关系但是它倒是的确是在 python 函数式编程是容易犯的一个错误我在这里也不妨介绍一下。foriinrange(10):passprint(i)# 9在程序里面经常会出现这类的循环语句python 的问题就在于当循环结束以后循环体中的临时变量 i 不会销毁而是继续存在于执行环境中所以在循环外部 i 的结果为 9。还有一个 python 的现象是python 的函数只有在执行时才会去找函数体里的变量的值。flist[]foriinrange(3):deffoo(x):print(xi)flist.append(foo)forfinflist:f(2)可能有些人认为这段代码的执行结果应该是 2,3,4。但是实际的结果是 4,4,4。loop 在 python 中是没有域的概念的flist 在向列表中添加 func 的时候并没有保存 i 的值而是当执行 f(2)的时候才去取这时候循环已经结束i 的值是 2所以结果都是 4。解决以上问题改写一下函数的定义就可以了。flist[]foriinrange(3):deffoo(x,yi):print(xy)flist.append(foo)forfinflist:f(2)闭包的作用闭包可以保存当前的运行环境以一个类似棋盘游戏的例子来说明。假设棋盘大小为 50*50左上角为坐标系原点0,0我需要一个函数接收 2 个参数分别为方向direction步长step该函数控制棋子的运动。这里需要说明的是每次运动的起点都是上次运动结束的终点。origin[0,0]legal_x[0,50]legal_y[0,50]defcreate(posorigin):defplayer(direction,step): 这里应该首先判断参数 direction,step 的合法性 比如 direction不能斜着走step不能为负等 然后还要对新生成的 xy 坐标的合法性进行判断处理 这里主要是想介绍闭包算法我就不详细写了。 new_xpos[0]direction[0]*step new_ypos[1]direction[1]*step pos[0]new_x pos[1]new_y# 注意此处不能写成 pos [new_x, new_y]因为参数变量不能被修改而 pos[] 是容器类的解决方法returnposreturnplayer playercreate()# 创建棋子 player起点为原点print(player([1,0],10))# 向x轴正方向移动10步print(player([0,1],20))# 向y轴正方向移动20步print(player([-1,0],10))# 向x轴负方向移动10步运行结果[10, 0] [10, 20] [0, 20]也就是我们先沿X轴前进了 10然后沿 Y 轴前进了 20然后反方向沿X轴退了 10坐标分别为[10,0], [10, 20], [0, 20]。当然闭包在爬虫以及 web 应用中都有很广泛的应用并且闭包也是装饰器的基础这些内容笔者会在后续的文章中分别介绍这里就不多谈了。理解了本文中的概念你应该知道的关于闭包的知识也差不多了请在自己的编程中尽情使用吧。3️⃣ 装饰器前面我们学习了闭包闭包从语法上来讲就是在函数体内定义一个函数而本节课我们要讲的装饰器就是利用闭包语法来实现项目的简洁化和模式化。装饰器经常用于有切面需求的场景比如插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计有了装饰器我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲装饰器的作用就是为已经存在的对象添加额外的功能。写个装饰器由于项目越来越庞大有时候客户反映程序用起来不像以前那么顺这时候技术负责人想让你编程对项目中每个函数进行性能测试 此时作为一线搬砖的你应该怎么办首先假如我们项目中有一些函数如下# 函数 1deffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2deffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3deffunc_three(data):foriinrange(1,data):data*ireturndata现在我们想看看测试这个函数的性能于是我们改写以上代码如下fromtimeimporttime 项目已有函数 # 函数 1deffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2deffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3deffunc_three(data):foriinrange(1,data):data*ireturndata 对项目已有函数的性能测试 beforetime()func_one(25000)aftertime()print(该函数耗时,(after-before))beforetime()func_two(25000)aftertime()print(该函数耗时,(after-before))beforetime()func_three(25000)aftertime()print(该函数耗时,(after-before))作为一个懒惰的程序员我们立马就发现了有一些重复的代码像喋喋不休的排比句一样反复出现执行一个函数并计算这个函数的执行时间。于是我们就可以把这个模式抽象出来改写代码如下fromtimeimporttime 项目已有函数 # 函数 1deffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2deffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3deffunc_three(data):foriinrange(1,data):data*ireturndata 对项目已有函数的性能测试 deftimer(func,data):beforetime()func(data)aftertime()return(after-before)print(该函数耗时,timer(func_one,25000))print(该函数耗时,timer(func_two,25000))print(该函数耗时,timer(func_three,25000))但这样还是很麻烦因为我们得改到所有的测试用例比如把 func_one(25000) 改成 timer(add, 25000) 等等。于是我们进一步改进让 timer 返回函数fromtimeimporttime 项目已有函数 # 函数 1deffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2deffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3deffunc_three(data):foriinrange(1,data):data*ireturndata 对项目已有函数的性能测试 deftimer(func):defwraper(data):beforetime()rstfunc(data)aftertime()print(该函数耗时,after-before)returnrstreturnwraper func_onetimer(func_one)func_twotimer(func_two)func_threetimer(func_three)func_one(25000)func_two(25000)func_three(25000)这里的最后一个问题是我们的 timer 包装的函数可能有不同的参数于是我们可以进一步用*args, **kwargs来传递参数只需要把上面的 timer 函数改成下面的形式即可deftimer(func):defwraper(*args,**kwargs):beforetime()rstfunc(*args,**kwargs)aftertime()print(该函数耗时,after-before)returnrstreturnwraper这里的 timer 函数就是一个“装饰器”它接受一个函数并返回一个新的函数。在装饰器的内部对原函数进行了“包装”。大家注意我们调用代码func_one timer(func_one)之后实际上相当于改写了 func_one 函数原项目中的关于 func_one 函数的调用都多了一个对 func_one 函数的进行性能测试的功能而对 func_one 函数本身的返回值并无影响。当然我们可以把性能测试的输出写到日志里面而不是输出到屏幕上。这种不需要改写原函数本身就可以增加原函数行为的方式非常符合软件工程中的“对增加开发对修改关闭”的设计原则。装饰器语法糖刚刚我们利用闭包的语法解决了项目中关于对所有函数进行性能测试的问题实际上我们自己写了一个装饰器但是我们都知道Python 是有很高的装逼姿势python 提供了一个装饰器的语法糖看起来很有逼格。当然我们也不能为了装逼而装逼凡事总得有个理由吧那我们就找一个我们发现我们自己写的装饰器代码最后调用部分代码如下func_one timer(func_one) func_two timer(func_two) func_three timer(func_three) func_one(25000) func_two(25000) func_three(25000)上面这个语句里我们把要测试性能的每个函数的名字重复了 3 次如果项目中函数改了名字我们的性能测试代码就得改 3 处。装逼的 Python 就想了一个更“好”的方法提供了一个语法来替换上面的内容fromtimeimporttimedeftimer(func):defwraper(*args,**kwargs):beforetime()rstfunc(*args,**kwargs)aftertime()print(该函数耗时,after-before)returnrstreturnwraper# 函数timerdeffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2timerdeffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3timerdeffunc_three(data):foriinrange(1,data):data*ireturndata func_one(25000)func_two(25000)func_three(25000)我们发现使用了装逼的语法糖后我们对每个要测试性能的函数只需要调用一次即可。使用语法糖后的装饰性和我们自己写的装饰器完全等价只是 写法更简洁一些。如果我们想给装饰器传参数应该怎么办比如我们想把每个要测试性能函数的函数名传给装饰器其实很简单我们给测试函数性能的装饰器再装饰一下就可以了。fromtimeimporttimedeftimer(funcname):defdecorate(func):defwraper(*args,**kwargs):beforetime()rstfunc(*args,**kwargs)aftertime()print(funcname耗时,after-before)returnrstreturnwraperreturndecorate# 函数timer(func_one函数)deffunc_one(data):foriinrange(1,data):dataireturndata# 函数 2timer(func_two函数)deffunc_two(data):foriinrange(1,data):data-ireturndata# 函数 3timer(func_three函数)deffunc_three(data):foriinrange(1,data):data*ireturndata func_one(25000)func_two(25000)func_three(25000)一个函数还可以同时定义多个装饰器装饰器的执行顺序是从里到外最先调用最里层的装饰器最后调用最外层的装饰器比如以下函数有三个装饰器该装饰器函数等同于f a(b(c(f)))。abcdeff():pass装饰器不仅可以是函数还可以是类相比函数装饰器类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的call方法当使用 形式将装饰器附加到函数上时就会调用此方法大家学完面向对象之后可以再看如下案例。fromtimeimporttimeclassTimer(object):def__init__(self,func):self._funcfuncdef__call__(self,arg):beforetime()rstself._func(arg)aftertime()print(该函数耗时,after-before)returnrstTimerdeffunc_one(data):foriinrange(1,data):dataireturndata func_one(25000)4️⃣ 装饰器适合处理什么日志、计时、缓存、权限检查等横跨多个函数的逻辑适合使用装饰器。业务核心逻辑不应全部藏进装饰器否则调用链会变得难以追踪。编写装饰器时还应保留原函数的名称、文档等元数据方便调试和生成文档。 文章小结函数可以作为值传递lambda适合简短表达式也可以由普通函数返回。闭包会引用外层变量循环中创建的闭包还要留意变量的延迟绑定装饰器通过包装函数扩展行为带参数时需要多一层函数。可写一个返回绝对值函数的函数观察循环生成的匿名函数何时读取循环变量再写装饰器转换函数返回的字符串。

相关新闻

衢州厂家直供切割式污水泵,带切割装置不易堵塞,适用于化粪池及餐饮废水排放

衢州厂家直供切割式污水泵,带切割装置不易堵塞,适用于化粪池及餐饮废水排放

说起切割式污水泵,先从基础常识聊起在城镇排水和污水收集处理体系中,排污泵是支撑整个系统连续运转的核心设备。传统的普通排污泵大多只针对常规无缠绕杂质的污水设计,而在实际使用场景中,我们遇到的污水往往成分复杂:…

2026/9/25 22:14:47 阅读更多 →
靠谱的多功能真空镀膜机源头生产厂家质量参考评选

靠谱的多功能真空镀膜机源头生产厂家质量参考评选

很多人选多功能真空镀膜机时,容易踩这4个大坑选多功能真空镀膜机,不少客户都有过糟心经历:要么买完设备参数虚标,实际量产时膜厚控制波动大、良率上不去;要么售后响应慢,机器出故障要等半个月才能修;还有的只卖裸机&am…

2026/9/25 22:14:47 阅读更多 →
跨境女装短视频怎么降本?从AI数字人到爆款复刻的实操方法

跨境女装短视频怎么降本?从AI数字人到爆款复刻的实操方法

TikTok 女装赛道的竞争,正在从“有没有产品”转向“能不能持续产出内容”。 同一款服装,可能需要测试不同模特、不同场景、不同开场台词和不同剪辑节奏。对于中小跨境团队来说,如果每条视频都依赖实拍,就要承担模特、场地、摄影、…

2026/9/25 22:13:47 阅读更多 →

最新新闻

ZoneDeck进程冻结与效率模式指南:挂起进程省CPU降内存,后台视频游戏秒停

ZoneDeck进程冻结与效率模式指南:挂起进程省CPU降内存,后台视频游戏秒停

ZoneDeck进程冻结与效率模式指南:挂起进程省CPU降内存,后台视频游戏秒停 【免费下载链接】ZoneDeck The Ultimate Workspace Manager, Switch between work and life, seamlessly生活工作无缝切换,专业的桌面工作区管理助手 项目地址: http…

2026/9/25 22:54:18 阅读更多 →
ARM64服务器Harbor v2.13.1离线安装全流程与常见坑解析

ARM64服务器Harbor v2.13.1离线安装全流程与常见坑解析

简介:面向ARM64架构的Harbor离线部署包,版本为当前最新的v2.13.1,专供在鲲鹏、飞腾等ARM处理器服务器上搭建镜像仓库使用,尤其适合Kubernetes与Docker离线环境下的运维场景。压缩包以tgz格式封装,共6个文件&#xff0c…

2026/9/25 22:54:18 阅读更多 →
Codex Router故障排查清单:从doctor诊断到rollback回滚的15个常见问题

Codex Router故障排查清单:从doctor诊断到rollback回滚的15个常见问题

Codex Router故障排查清单:从doctor诊断到rollback回滚的15个常见问题 【免费下载链接】codex-router External-model router for Codex with guided Kimi OAuth/API, DeepSeek, safe migration, and rollback. 项目地址: https://gitcode.com/gh_mirrors/co/code…

2026/9/25 22:54:18 阅读更多 →
Harbor v2.13.1 ARM64离线安装包制作与部署避坑指南

Harbor v2.13.1 ARM64离线安装包制作与部署避坑指南

简介:面向ARM64架构服务器的Harbor v2.13.1离线安装包,专为在鲲鹏、飞腾等国产化平台及树莓派环境中部署Docker镜像仓库的运维、开发人员准备。由于官方安装包长期以x86架构为主要分发对象,该资源精准补齐ARM设备无法直接使用离线包的短板&am…

2026/9/25 22:54:18 阅读更多 →
杭州大平层全案整体设计服务商实力与用户口碑深度解析

杭州大平层全案整体设计服务商实力与用户口碑深度解析

什么是大平层全案整体设计大平层这类改善型住宅,拥有开阔的空间面积和优越的地段资源,已经成为众多改善型家庭的置业,而全案整体设计是适配大平层空间的专属家居服务模式,和传统家居服务有着本质区别。传统家居消费中,…

2026/9/25 22:54:18 阅读更多 →
向 Kiro Crew 贡献代码:从 Makefile 构建到 pytest 质量门禁的开发者完整指南

向 Kiro Crew 贡献代码:从 Makefile 构建到 pytest 质量门禁的开发者完整指南

向 Kiro Crew 贡献代码:从 Makefile 构建到 pytest 质量门禁的开发者完整指南 【免费下载链接】KiroCrew A persistent workspace for development work that self-improves and continues beyond one session. 项目地址: https://gitcode.com/gh_mirrors/ki/Kiro…

2026/9/25 22:53:18 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/25 19:27:14 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/25 11:15:26 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/25 20:29:09 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/25 20:29:43 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/25 20:29:31 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/25 19:27:26 阅读更多 →