python进阶(12)闭包
闭包
首先了解一下:如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。
在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10
# inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
# 在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo = outer(5)
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2()#17
从上面例子是我写的一个最简单的很典型的闭包。我估计如果是初学的小伙伴,可能很多名词都不明白是什么意思,没关系,我把这些名词按照自己的理解去解释一下~
外函数返回了内函数的引用
引用是什么?在python中一切都是对象,包括整型数据1,函数,其实是对象。
当我们进行a=1的时候,实际上在内存当中有一个地方存了值1,然后用a这个变量名存了1所在内存位置的引用。引用就好像c语言里的指针,大家可以把引用理解成地址。a只不过是一个变量名字,a里面存的是1这个数值所在的地址,就是a里面存了数值1的引用。
相同的道理,当我们在python中定义一个函数def demo(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo, 这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。
有了上面的解释,我们可以继续说,返回内函数的引用是怎么回事了。对于闭包,在外函数outer中 最后return inner,我们在调用外函数 demo = outer() 的时候,outer返回了inner,inner是一个函数的引用,这个引用被存入了demo中。所以接下来我们再进行demo() 的时候,相当于运行了inner函数。
同时我们发现,一个函数,如果函数名后紧跟一对括号,相当于现在我就要调用这个函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
外函数把临时变量绑定给内函数
按照我们正常的认知,一个函数结束的时候,会把自己的临时变量都释放还给内存,之后变量都不存在了。一般情况下,确实是这样的。但是闭包是一个特殊情况。外部函数发现,自己的临时变量会在将来的内部函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量送给内函数绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。
在我编写的实例中,我两次调用外部函数outer,分别传入的值是5和7。内部函数只定义了一次,我们发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。python中一切都是对象,虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。虽然内函数代码是一样的,但其实,我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。
闭包中内函数修改外函数局部变量
在闭包内函数中,我们可以随意使用外函数绑定来的临时变量,但是如果我们想修改外函数临时变量数值的时候发现出问题了!
在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
- 1 global 声明全局变量
- 2 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
- 在python3中,可以用
nonlocal关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。 - 在python2中,没有
nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
#修改闭包变量的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer(a):
b = 10 # a和b都是闭包变量
c = [a] #这里对应修改闭包变量的方法2
# inner是内函数
def inner():
#内函数中想修改闭包变量
# 方法1 nonlocal关键字声明
nonlocal b
b += 1
# 方法二,把闭包变量修改成可变数据类型 比如列表
c[0] += 1
print(c[0])
print(b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
demo = outer(5)
demo()
# 结果
6
11
从上面代码中我们能看出来,在内函数中,分别对闭包变量进行了修改,打印出来的结果也确实是修改之后的结果。以上两种方法就是内函数修改闭包变量的方法。
还有一点需要注意:使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量
def outer(x):
def inner(y):
nonlocal x
x += y
return x
return inner
a = outer(10)
print(a(1))
print(a(3))
# 结果
11
14
两次分别打印出11和14,由此可见,每次调用inner的时候,使用的闭包变量x实际上是同一个。
参考资料:https://www.cnblogs.com/s-1314-521/p/9763376.html
python进阶(12)闭包的更多相关文章
- python进阶12 Redis
python进阶12 Redis 一.概念 #redis是一种nosql(not only sql)数据库,他的数据是保存在内存中,同时redis可以定时把内存数据同步到磁盘,即可以将数据持久化,还提 ...
- Python进阶之闭包
闭包 .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB","S ...
- Python 进阶_闭包 & 装饰器
目录 目录 闭包 函数的实质和属性 闭包有什么好处 小结 装饰器 更加深入的看看装饰器的执行过程 带参数的装饰器 装饰器的叠加 小结 装饰器能解决什么问题 小结 闭包 Closure: 如果内层函数引 ...
- Python 进阶 之 闭包变量
在闭包内访问外部变量的方法有两种: 1:变量前加nonlocal(仅支持Python3) 2:用列表来代替变量.即使是只有一个元素的数组,否则会报错.
- Python进阶(五)----内置函数Ⅱ 和 闭包
Python进阶(五)----内置函数Ⅱ 和 闭包 一丶内置函数Ⅱ ####内置函数#### 特别重要,反复练习 ###print() 打印输入 #sep 设定分隔符 # end 默认是换行可以打印到 ...
- python进阶学习笔记(一)
python进阶部分要学习的内容: 学习目标: 1.函数式编程 1.1,什么是函数式编程 函数式编程是一种抽象计算的编程模式 不同语言的抽象层次不同: 函数式编程的特点: python支持的函数式编程 ...
- Python进阶 函数式编程和面向对象编程等
函数式编程 函数:function 函数式:functional,一种编程范式.函数式编程是一种抽象计算机的编程模式. 函数!= 函数式(如计算!=计算机) 如下是不同语言的抽象 层次不同 高阶函数: ...
- Python进阶之函数式编程
函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...
- python进阶强化学习
最近学习了慕课的python进阶强化训练,将学习的内容记录到这里,同时也增加了很多相关知识. 主要分为以下九个模块: 基本使用 迭代器和生成器 字符串 文件IO操作 自定义类和类的继承 函数装饰器和类 ...
- Python进阶之面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...
随机推荐
- linux(11)配置环境变量
前言 在自定义安装软件的时候,经常需要配置环境变量,下面进行详细解析 & nbsp; 环境变量配置文件 用户 配置文件 系统环境 /ect/profile /etc/bashrc /etc/e ...
- Codeforces Round #627 (Div. 3) C - Frog Jumps(逻辑)
题意: 有一个每个单元标明移动方向的长为n的序列,每次移动不能超过距离k,问能够从0移动到n+1的k的最小值. 思路: k=最长连续L序列长度+1. #include <bits/stdc++. ...
- Educational Codeforces Round 89 (Rated for Div. 2) A. Shovels and Swords(贪心/数学)
题目链接:https://codeforces.com/contest/1366/problem/A 题意 有两个数 $a$ 和 $b$,每次可以选择从一个数中取 $2$,另一个数中取 $1$,问最多 ...
- P3399 丝绸之路(DP)
题目背景 张骞于公元前138年曾历尽艰险出使过西域.加强了汉朝与西域各国的友好往来.从那以后,一队队骆驼商队在这漫长的商贸大道上行进,他们越过崇山峻岭,将中国的先进技术带向中亚.西亚和欧洲,将那里的香 ...
- Atcoder ABC161 A~E
传送门 A - ABC Swap 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 ...
- 使用dotNET_Reactor4.7加密后的dll在VS2010中无法打包
1.只要去除加密工具中的反编译选项"Anti ILDASM",再加密就OK了. 2.或者使用VS2008打包也行.
- python 调用麦克风;摄像头;截屏;
# -*- coding: utf-8 -*- """ Created on Mon Jun 24 14:47:35 2019 @author: erio "& ...
- Leetcode(105)-从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树. 注意:你可以假设树中没有重复的元素. 例如,给出 前序遍历 preorder = [3,9,20,15,7] 中序遍历 inorder = [9,3,15, ...
- IDEA 安装常用操作一
关于IDEA的下载,破解自行百度 一.安装完成的常用设置 SDK选择.编译版本的选择,单项目选择,全局选择 maven配置,单项目,全局配置 二.IDEA如何安装lombok https://www. ...
- 51nod1089 最长回文子串 manacher算法
0. 问题定义 最长回文子串问题:给定一个字符串,求它的最长回文子串长度. 如果一个字符串正着读和反着读是一样的,那它就是回文串.下面是一些回文串的实例: 12321 a aba abba aaaa ...