Python 闭包和装饰器,深浅拷贝

发布时间:2026/10/13 6:44:03
Python 闭包和装饰器,深浅拷贝 1.闭包1.1 为什么要闭包闭包的作用闭包可以保存函数内的局部变量而不会随着函数调用结束而被销毁即延长了外部函数内局部变量的生命周期1.2 闭包定义闭包在函数嵌套的前提下内部函数访问了外部函数的局部变量包括外部函数的参数并且外部函数返回了内部函数这种使用外部函数变量的内部函数称为闭包代码示例如下注意return 内部函数名表示返回整个内部函数对象return 内部函数名表示返回内部函数的返回结果函数名可以直接赋值给变量这个变量就是函数对象def get_sum(a, b): return a b my_sum get_sum # my_sum 就是函数对象因此对于闭包中外部函数的理解就是定义了一个函数返回结果是对象1.3 闭包的构成条件有函数嵌套外部函数内部再定义内部函数有外部变量引用内部函数引用了外部函数的局部变量还包括外部函数的参数有返回内部函数名外部函数返回了内部函数名例子如下定义一个用于求和的闭包1.4 闭包的内存图解1.5 nonlocal 关键字gloabal声明全局变量使得函数能够修改全局变量nonlocal声明外部函数变量使得内部函数能够修改外部函数变量正常情况下内部函数是无法直接修改外部函数变量的如下所示使用nonlocal 声明外部函数变量后便可以通过内部函数修改外部变量如下所示本质来讲默认外层函数执行完成后函数栈帧释放。闭包会让外层局部变量逃逸到堆内存保留下来。在内部函数中如果要重新赋值外层嵌套变量必须使用nonlocal声明它表示变量来自上一层函数作用域而非当前局部作用域相关内存图解如下1.6 小结2.装饰器2.1 什么是装饰器装饰器接收函数作为参数返回一个新函数的闭包作用不改变原有函数的基础上给原有函数增加额外的功能。装饰器本质上就是一个闭包函数装饰器的构成条件有函数嵌套外部函数内部再定义内部函数有外部变量引用内部函数引用了外部函数的局部变量还包括外部函数的参数有返回内部函数名外部函数返回了内部函数名有额外功能给需要装饰的原有函数增加额外的功能2.2 装饰器语法2.2.1 装饰器定义注意对于传统用法被装饰的函数和装饰器位置无要求对于语法糖用法装饰器必须在被装饰函数之前2.2.2 用法2.2.2.1 传统用法装饰后的函数名 装饰器名被装饰的函数名 装饰后的函数名 #函数调用相关代码如下#定义装饰器 def check_login(fuc_name): #1.1 函数嵌套定义内部函数 def fuc_inner(): #1.2 额外功能 print(校验登录... 登录成功) #1.3 访问原函数即内部函数引用外部函数变量 fuc_name() #1.4 返回内部函数名 return fuc_inner def comment(): print(评论...) def payment(): print(支付...) comment check_login(comment) comment()相关图解如下2.2.2.2 语法糖常用在要被装饰的原函数上方一行直接写 装饰器名之后直接调用原函数相关代码如下#定义装饰器 def check_login(fuc_name): #1.1 函数嵌套定义内部函数 def fuc_inner(): #1.2 额外功能 print(校验登录... 登录成功) #1.3 访问原函数即内部函数引用外部函数变量 fuc_name() #1.4 返回内部函数名 return fuc_inner check_login #底层其实是 comment check_login(comment) def comment(): print(评论...) check_login #底层其实是 payment check_login(payment) def payment(): print(支付...) comment()2.3 装饰器的使用2.3.1 装饰无参无返回值的函数格式如下案例在无参无返回值的原有函数求和计算结果之前添加一个友好提示“正在努力计算中”2.3.2 装饰有参无返回值的函数格式如下案例在有参无返回值的原有函数求和计算结果之前添加一个友好提示“正在努力计算中”注意装饰器的内部函数格式与被装饰的原函数一致2.3.3 装饰无参有返回值的函数格式如下案例在无参有返回值的原有函数求和计算结果之前添加一个友好提示“正在努力计算中”2.3.4 装饰有参有返回值的函数格式如下案例在有参有返回值的原有函数求和计算结果之前添加一个友好提示“正在努力计算中”2.3.5 装饰可变参数的函数以有返回值为例格式如下案例定义一个可以计算多个数据和多个字典 value 值之和的函数并调用。在原有函数求和计算结果之前添加一个友好提示“正在努力计算中”#定义修饰器 def decorator(fuc_name): def fuc_inner(*args, **kwargs): print(正在努力计算中...) return fuc_name(*args, **kwargs) return fuc_inner #装饰get_sum函数 decorator def get_sum(*args, **kwargs): :param args: 数字元组 :param kwargs: 数字字典 :return: 结果之和 return sum(args) sum(kwargs.values()) #调用函数 sum get_sum(10, 20, 30, 40, 50, a 100, b 200, c 300) print(结果为, sum)结果如下2.3.6 多个装饰器的使用多个装饰器装饰一个函数多个装饰器的装饰过程是离函数最近的装饰器先装饰然后外面的装饰器再进行装饰由内到外的装饰过程注意装饰器遵循由内到外的装饰过程当使用语法糖写法时代码执行效果上是从上往下执行的当使用传统写法时代码书写要按装饰过程由内到外的过程从上往下书写案例分析发表评论之前都是需要先登录用户 再 进行验证码验证。先定义有发表评论的功能函数然后在不改变原有函数的基础上需要先检查用户登录和输入验证码#1.定义登录装饰器 def decorator_login(func): def fuc_inner(): print(登录中...) func() return fuc_inner #2.定义评论装饰器 def decorator_verify(func): def fuc_inner(): print(验证验证码中...) func() return fuc_inner #3.定义并装饰评论函数 decorator_login #后装饰登录 decorator_verify #先装饰验证 def comment(): print(发表评论) #4.调用函数 comment()结果如下相关图解如下2.3.7 带有参数的装饰器(三层函数版使用带参的装饰器其实是在装饰器外面又包裹了一个函数使用该函数接收参数返回装饰器带有参数的装饰器本质是一个3层函数带参装饰器 外层函数参数接收装饰器返回一个无参装饰器三层结构第 1 层接收装饰器自己的参数第 2 层接收被装饰的函数第 3 层接收函数调用时的实参注意装饰器的参数只能有一个为什么要使用带参数的装饰器普通装饰器逻辑固定功能固定。相当于这个装修队只会干一种活比如装灯其他的不会干比如贴瓷砖粉刷墙壁带参数装饰器相当于装饰器工厂用来动态生成「不同配置的装饰器」实现装饰器的可配置复用。相当于这个装修队能干多个活比如装灯贴瓷砖粉刷墙壁做防水都会干因此只聘用这一个装修队就能完成大部分装修工作格式如下注意里面的外层函数可以理解为一个简单的外部函数它可以接收多个来自装饰器的参数案例定义一个既能装饰减法运算又能装饰加法运算的装饰器即带有参数的装饰器def outer_func(decorator_paras): def decorator(func): def inner_func(a, b): if operation : print(正在执行加法) elif operation -: print(正在执行减法) return func(a, b) return inner_func return decorator outer_func() def sum(a, b): return a b outer_func(-) def sub(a, b): return a - b print(sum(10, 5)) print(sub(10, 5))2.3.8 带有参数的装饰器(优化两层版通过合理利用传入装饰器的函数对象参数可以进行简化代码如下#定义装饰器 def decorator(func): #func接收函数对象 def inner_func(a, b): if func.__name__ sum: print(正在执行加法) elif func.__name__ sub: print(正在执行减法) return func(a, b) return inner_func decorator def sum(a, b): return a b decorator def sub(a, b): return a - b print(sum(10, 5)) print(sub(10, 5))结果如下2.4 深浅拷贝2.4.1 概述1.所谓的深浅拷贝分别指的是copy 模块的 copy函数 --------- 浅拷贝copy 模块的 deepcopy函数 --------- 深拷贝2.通俗理解深浅拷贝浅拷贝拷贝的浅深拷贝拷贝的深3.深浅拷贝只针对可变类型来讲的浅拷贝只拷贝第1层最外层深拷贝递归拷贝所有层4.如果是针对不可变类型则用法和变量赋值一样并无区别几乎没有意义2.4.2 可变类型与不可变类型判断标准对象的值能不能在原内存地址上直接修改不可变类型对象值一旦创建内存里的数据不能原地修改“修改” 本质是新建一个对象分配新地址。包括字符串元组数值类型整型浮点型布尔类型可变类型对象可以在同一个内存地址原地修改内容对象本身不重新创建。包括列表字典集合自定义类实例2.4.3 变量赋值a PythonPython解释器干的事情创建变量a创建一个对象分配一块内存来存储字符串 “Python”将变量与对象通过指针连接起来从变量到对象的连接称之为引用变量引用对象Python 的变量保存在命名空间字典里不是普通 C 语言那种独立内存格子命名空间本质{ “a”对象地址}变量名只是字典的 keyvalue 才是对象的引用地址a python print(id(a)) # 打印的就是字符串对象的内存地址案例变量赋值内存关系起初变量 c 和变量 d指向完全同一个列表对象d.append(40)变量 c 和变量 d还是指向同一个对象iddidcc 的值同步变化注意变量 新对象换箭头只改变这个变量指向谁和另一个变量无关变量.方法名/ 变量[ 下标 ] 值不改箭头修改同一个对象内部的数据所有指向这个对象的变量都会看到变化可变类型和不可变类型在变量赋值中内存关系没有区别都是变量 c 和变量 d指向完全同一个对象2.4.4 浅拷贝浅拷贝创建新对象其内容是原对象的引用地址语法copy.copy浅拷贝之所以称为浅拷贝是它仅仅拷贝第一层容器拷贝了最外围的对象本身内部的元素都只是拷贝了一个引用而已第一层是什么假设原始数据import copy origin [21, 22, [1,2], [11,12]] a copy.copy(origin) # a是origin的浅拷贝我们把这个嵌套列表分层第一层外层列表 a 直接保存的 4 个元素21int不可变22int不可变[1, 2]list可变[11, 12]list可变第二层子列表里面的值比如 [1, 2] 里面的 12属于内层不是第一层通俗理解外壳换新房子里面家具共用一套2.4.4.1 可变类型浅拷贝创建新对象import copy def demo2_copy_changeable(): a [1, 2, 3] # b [11, 12, 13] c [21, 22, a, b] d copy.copy(c) #浅拷贝 print(id(a)) #0x01 print(id(b)) #0x02 print(id(c)) #0x03 外层地址C print(id(d)) #0x04 外层地址D(≠C) #c,d内层子列表地址完全相同 print(id(c[2])) #0x01 print(id(d[2])) #0x01 #修改内层可变对象,c和d一起变 a[2] 44 print(c) #[21, 22, [1, 2, 44], [11, 12, 13]] print(d) #[21, 22, [1, 2, 44], [11, 12, 13]] #修改第一层不可变对象,互不影响 c[0] 33 print(c) #[33, 22, [1, 2, 44], [11, 12, 13]] print(d) #[21, 22, [1, 2, 44], [11, 12, 13]] demo2_copy_changeable()其内存图解如下2.4.4.2 不可变类型浅拷贝变量赋值def demo2_copy_unchangeable(): a (1, 2, 3) # b (11, 12, 13) c (21, 22, a, b) d copy.copy(c) #浅拷贝 print(id(a)) #0x01 print(id(b)) #0x02 print(id(c)) #0x03 print(id(d)) #0x03 #c,d内层元素地址完全相同 print(id(c[2])) #0x01 print(id(d[2])) #0x01 demo2_copy_unchangeable()其内存图解如下2.4.5 深拷贝深拷贝递归拷贝所有层级的可变对象每一层都创建全新独立对象语法copy.deepcopy外层新对象里面嵌套的可变对象全部重新开辟内存复制一份两个对象彻底隔离修改任意一方完全互不影响通俗理解房子全部重新盖里面家具全部重新对着旧的买一套2.4.5.1 可变类型深拷贝def demo3_deepcopy_changeable(): a [1, 2, 3] # b [11, 12, 13] c [21, 22, a, b] d copy.deepcopy(c) #深拷贝 print(id(a)) #0x01 print(id(b)) #0x02 print(id(c)) #0x03 外层地址C print(id(d)) #0x04 外层地址D(≠C) print(id(c[2])) #0x01 print(id(d[2])) #0x05≠0x01 (注意深拷贝时会拷贝所有层级的可变对象,每一层都创建全新独立对象) #修改可变对象,c变d不变 a[2] 44 print(c) #[21, 22, [1, 2, 44], [11, 12, 13]] print(d) #[21, 22, [1, 2, 3], [11, 12, 13]] demo3_deepcopy_changeable()其内存图解如下2.4.5.2 不可变类型深拷贝def demo3_deepcopy_unchangeable(): a (1, 2, 3) b (11, 12, 13) c (21, 22, a, b) d copy.deepcopy(c) #深拷贝 print(id(a)) #0x01 print(id(b)) #0x02 print(id(c)) #0x03 print(id(d)) #0x03 #c,d内层元素地址完全相同 print(id(c[2])) #0x01 print(id(d[2])) #0x01 demo3_deepcopy_unchangeable()其内存图解如下

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询