Python进阶系列之-闭包和装饰器

📅 2026/6/29 20:12:51
Python进阶系列之-闭包和装饰器
Python进阶系列之-闭包和装饰器写在前面在Python进阶的道路上有两个“拦路虎”让无数小白直呼头秃它们就是闭包和装饰器。很多同学背了八股文知道它们是什么但一到实际应用就懵圈。其实它们并没有那么神秘闭包与装饰器是Python函数式编程的核心知识点也是Python进阶路上的必经之路。装饰器基于闭包实现可以在不修改原函数源码、不改变原函数调用方式的前提下为函数扩展额外功能完美契合软件开发的“开闭原则”。本文从基础概念出发逐层拆解闭包的底层原理、装饰器的多种实现写法与执行逻辑全程配合内存图解与可运行代码帮你彻底吃透这一面试高频考点。今日内容大纲函数名的用法万物皆对象闭包详解装饰器详解文章目录Python进阶系列之-闭包和装饰器一、前置知识函数名也是对象1.1 函数名是对象支持赋值1.2 函数名可以作为参数传递二、闭包封装变量的函数嵌套2.1 什么是闭包?2.2 基础闭包示例与执行流程闭包执行流程内存图解2.3 nonlocal关键字修改外部函数变量nonlocal内存图解2.4 闭包的核心价值三、装饰器无侵入式功能增强3.1 装饰器的本质与作用3.2 装饰器入门两种写法方式1传统手动包装写法方式2语法糖写法推荐3.3 装饰器的四种形态重点细节场景1装饰无参无返回值函数场景2装饰有参无返回值函数场景3装饰有参有返回值函数场景4装饰无参有返回值函数3.5 多个装饰器的执行顺序多装饰器执行顺序图解3.6 带参数的装饰器四、全文总结一、前置知识函数名也是对象在讲闭包之前必须先扭转一个观念在Python中万物皆对象。函数也不例外函数名本质上是函数对象的内存地址函数入口这是闭包和装饰器能够成立的底层基础。1.1 函数名是对象支持赋值函数名存储的是函数对象的引用因此可以像普通变量一样赋值给其他变量赋值后的变量等价于原函数可以直接调用。我们平时写的函数名比如func01它其实就是一个变量里面存的是这个函数在内存中的地址函数入口。而func01()加上括号才表示“调用这个函数”。# 需求: 定义1个无返回值的func01(), 并直接输出函数名.deffunc01():print(hello world!)if__name____main__:# 1. 直接打印函数名输出的是内存地址print(func01)# function func01 at 0x00000288D53E8C10# 2. 调用函数print(func01())# 先执行func01打印hello world!然后打印返回值None# 3. 赋值操作把函数名当成对象赋值给另一个变量ffunc01print(f)# 地址和上面一样说明f也指向这个函数# 4. 间接调用f()# 相当于调用了 func01()1.2 函数名可以作为参数传递既然函数名是对象那它当然也能作为参数传递给另一个函数这种把“函数A传给函数B并在B中调用A”的写法在Python中称为回调函数。。# 1. 定义1个无参函数 method()defmethod():print(我是 method 函数)# 2. 定义1个有参数的函数 func()接收一个函数对象deffunc(fn):fn()# 在内部调用传进来的函数# 3. 调用: 把函数名 method 传给 funcfunc(method)# 输出: 我是 method 函数小白理解函数就像是一张“菜谱”函数名就是菜谱的名字。把函数名传给别人就是把菜谱递给别人别人拿到菜谱后按上面的步骤做菜加括号调用。二、闭包封装变量的函数嵌套2.1 什么是闭包?简单来说引用了外部函数变量的内部函数就叫做闭包。闭包最大的作用是保存函数内的局部变量让变量不会随着外部函数调用完毕而销毁延长了局部变量的生命周期。大白话解释在一个函数里面又定义了一个函数而且里面的函数用到了外面函数的变量。这就相当于内部函数把外部变量的值“包”了起来带走了。形成闭包必须同时满足三个条件(死记硬背有嵌套函数嵌套定义分为外部函数和内部函数有引用内部函数使用了外部函数的变量包括形参有返回外部函数返回内部函数名函数对象标准格式def外部函数名(形参列表):# 外部函数变量def内部函数名(形参列表):# 使用外部函数的变量...return内部函数名2.2 基础闭包示例与执行流程我们以一个求和闭包为例拆解闭包的完整执行过程。# 需求: 定义用于求和的闭包. 外部函数有参数num1, 内部函数有参数num2.deffun_outer(num1):# 内部函数deffun_inner(num2):# 1. 有嵌套# 引用外部函数的变量 num1sum_valnum1num2# 2. 有引用 (使用了外部函数的num1)print(fsum的值:{sum_val})# 外部函数返回内部函数对象returnfun_inner# 3. 有返回 (返回内部函数名)# 1. 调用外部函数得到内部函数的引用ffun_outer(10)# 细节: f就是内部函数对象, 即: f fun_inner。此时外部的num110被保存了下来f(1)# 11 (10 1)f(1)# 11 (10 1)f(1)# 11 (10 1)# 为什么输出全是11 因为每次调用 f(1) 时外部函数的 num1 始终被闭包保存在内存里值就是 10。# 2. 多次调用内部函数都能访问到外部函数的变量f(1)# 11f(2)# 12f(3)# 13闭包执行流程内存图解闭包的执行可以分为5个核心步骤内存中变量的生命周期如下图所示调用fun_outer(10)在栈中执行外部函数创建变量num110外部函数执行完毕返回内部函数对象的地址赋值给变量f调用f(1)执行内部函数内部函数访问外部函数保留的num1完成计算并输出内部函数执行完毕外部函数的变量依然被闭包保留不会销毁2.3 nonlocal关键字修改外部函数变量在闭包中内部函数默认只能读取外部函数的变量如果要修改外部函数的变量值必须使用nonlocal关键字声明否则会报错。对比记忆global声明修改全局变量在整个py文件中生效nonlocal声明修改外部函数的变量在闭包嵌套层级中生效deffun_outer():# 有嵌套a100# 外部函数的变量deffun_inner():# 有嵌套# 核心细节: 在内部函数中修改外部函数的变量值, 要通过 nonlocal 关键字实现.nonlocala aa1# 修改外部变量aprint(fa的值为:{a})# 有引用returnfun_inner# 有返回# 调用fnfun_outer()# fn fun_innerfn()# a变成101fn()# a变成102fn()# a变成103nonlocal内存图解使用nonlocal后内部函数修改的是外部函数同一块内存中的变量变量状态会持续保留2.4 闭包的核心价值保留函数执行后的状态让局部变量的生命周期延长实现数据私有化外部无法直接访问闭包内的变量是装饰器实现的底层基础三、装饰器无侵入式功能增强3.1 装饰器的本质与作用装饰器本质上就是一个闭包函数它的核心作用是在不修改原函数源码、不改变原函数调用方式的前提下为原函数扩展额外功能比如日志打印、登录校验、性能计时等。装饰器相比闭包多了一个核心特点接收原函数作为参数在内部函数中调用原函数并在前后插入增强逻辑。大白话解释装饰器本质上就是闭包的一种写法。它的作用是在不改变原有函数源码、不改变原有函数调用方式的基础上给原有函数增加新的功能做增强。前提条件4点1.有嵌套 2. 有引用 3. 有返回 4. 有额外功能语法糖在要被装饰的函数上写 装饰器名之后正常调用函数即可Python会自动帮我们做增强。3.2 装饰器入门两种写法我们以「发表评论前需要先登录」为例讲解装饰器的两种实现方式。方式1传统手动包装写法# 1.定义装饰器(无参无返回值)defcheck_user(fn):# fn是要被装饰的函数名definner():print(登录中...)# 4. 有额外功能fn()# 2. 有引用 (调用原函数)returninner# 3. 有返回# 2. 定义原函数并使用语法糖check_userdefcomment():print(发表评论!)# 3. 测试if__name____main__:comment()# 直接调用会先打印登录中...再打印发表评论!方式2语法糖写法推荐Python提供了装饰器名的语法糖效果和手动包装完全一致代码更简洁优雅。# 定义装饰器defcheck_user(fn):definner():print(登录中...)fn()returninner# 在原函数上添加语法糖check_userdefcomment():print(发表评论!)# 直接调用原函数即可自动生效增强功能comment() 本质说明check_user写在函数上等价于执行了comment check_user(comment)3.3 装饰器的四种形态重点细节核心原则装饰器的内部函数格式必须和原函数格式保持一致 原函数有参数内部函数也要有原函数有返回值内部函数也要返回。为了省事我们通常直接写通用装饰器(使用可变参数*args和**kwargs接收任意参数)适配所有情况# 通用装饰器写法使用 *args, **kwargs 接收任意参数defprint_info(fn):definner(*args,**kwargs):# 适配原函数的各种参数print([友好提示] 正在努力计算中!)# 额外功能resultfn(*args,**kwargs)# 调用原函数并接收返回值returnresult# 返回原函数的执行结果returninner# 测试有参有返回值的函数print_infodefget_sum(*args,**kwargs):sum0foriinargs:sumiforvalueinkwargs.values():sumvaluereturnsumif__name____main__:print(get_sum(1,2,3,a10,b20))# 结果: 36场景1装饰无参无返回值函数defprint_info(fn):definner():print([友好提示] 正在努力计算中!)fn()returninnerprint_infodefget_sum():a10b20print(fsum的值为:{ab})get_sum()场景2装饰有参无返回值函数defprint_info(fn):definner(a,b):print([友好提示] 正在努力计算中!)fn(a,b)returninnerprint_infodefget_sum(a,b):print(fsum的值:{ab})get_sum(10,20)场景3装饰有参有返回值函数defprint_info(fn):definner(a,b):print([友好提示] 正在努力计算中!)resultfn(a,b)# 接收原函数返回值returnresult# 向外返回结果returninnerprint_infodefget_sum(a,b):returnabprint(get_sum(1,2))场景4装饰无参有返回值函数defprint_info(fn):definner():print([友好提示] 正在努力计算中!)returnfn()returninnerprint_infodefget_sum():return1020print(get_sum())3.5 多个装饰器的执行顺序一个函数可以同时被多个装饰器装饰核心规则装饰顺序由内到外依次包装先靠近函数的装饰器先生效执行顺序由外到内依次执行先运行外层装饰器的前置逻辑再往里走以「先登录、再验证码校验」为例# 装饰器1登录defcheck_login(fn):definner():print(登录中...)fn()returninner# 装饰器2验证码defcheck_code(fn):definner():print(校验验证码...)fn()returninner# 使用多个语法糖先验证码后登录还是先登录后验证码check_login# 第1步先执行外层check_code# 第2步后执行内层defcomment():print(发表评论!)if__name____main__:comment() 输出结果 登录中... 校验验证码... 发表评论! 多装饰器执行顺序图解多个装饰器的包装与执行流程可以用下图直观理解3.6 带参数的装饰器普通的装饰器只能传一个参数就是原函数 fn。如果希望装饰器可以接收自定义参数实现差异化的功能增强就是装饰器本身也需要接收参数怎么办比如根据不同的运算符号给出不同的提示。结论在原来的装饰器外面再套一层函数# 1. 定义带参数的装饰器deflogging(flag):# 接收装饰器自身的参数defdecorator(fn):# 接收原函数definner():# 根据传入的flag做不同的额外功能ifflag:print(---[友好提示] 正在努力计算 加法 运算中 ---)elifflag-:print(---[友好提示] 正在努力计算 减法 运算中 ---)fn()returninnerreturndecorator# 返回真正的装饰器# 2. 使用带参数的装饰器logging()# 先调用logging()返回decorator然后再用decorator去装饰add函数defadd():print(我是加法运算!)logging(-)defsubstract():print(我是减法运算!)# 3. 测试if__name____main__:add()print(-*31)substract()四、全文总结知识点核心要义必要条件/口诀函数名是对象是内存地址加括号是调用不加括号是传递对象闭包内函数用外函数的变量有嵌套、有引用、有返回装饰器不改原码增强功能有嵌套、有引用、有返回、有额外功能通用装饰器适配所有原函数内部函数写*args, **kwargs并return fn(...)多装饰器装饰多个功能语法糖从上往下执行带参装饰器装饰器自身需要参数外面再加一层函数返回真正的装饰器函数是对象Python中函数是对象函数名是引用可以赋值、作为参数传递、作为返回值。闭包三要素函数嵌套、内函数引用外函数变量、外函数返回内函数名核心作用是保留变量状态。nonlocal关键字闭包中修改外部函数变量时必须声明区别于操作全局变量的global。装饰器本质基于闭包实现无侵入式增强函数功能符合开闭原则。装饰器核心规则内部函数的参数、返回值要与原函数对齐通用装饰器用*args, **kwargs适配所有函数。多装饰器顺序装饰时由内到外执行时由外到内。带参装饰器外层多一层函数接收自定义参数返回标准装饰器即可。 避坑指南写装饰器时最容易犯的错误就是内部函数的格式和原函数不一致。如果你不确定原函数有没有参数、有没有返回值请直接使用通用装饰器*args, **kwargs和return缺一不可结束语闭包和装饰器是Python进阶的分水岭、是Python高阶语法的精髓掌握它们不仅能写出更优雅的代码也是理解框架源码、应对面试的必备技能。后续学习Django、Flask等Web框架时装饰器无处不在。多写几遍代码画画内存图你会发现不过如此如果这篇博客对你有帮助记得点赞收藏哦有任何疑问欢迎在评论区留言交流~