一. 闭包
1、闭包概念
- 在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包
- 一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。
- 但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
2、闭包特点
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
闭包实例:
#闭包函数的实例
def outer( a ):
b = 10
def inner(): # 在内函数中 用到了外函数的临时变量
print(a+b) # 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
demo = outer(5)
demo() # 15
# 在这里我们调用外函数传入参数5
# 此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
3、闭包中内函数修改外函数局部变量
1、在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
1) global 声明全局变量
2) 全局变量是可变类型数据的时候可以修改
2、在闭包情况下使用下面两种方法修改
1)在python3中,可以用nonlocal 关键字声明 一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
2)在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。
闭包中内函数修改外函数局部变量:
#修改闭包变量的实例
def outer( a ):
b = 10 # a和b都是闭包变量
c = [a] # 这里对应修改闭包变量的方法2
def inner():
# 方法一: nonlocal关键字声明(python3)
nonlocal b
b+=1
# 方法二: 把闭包变量修改成可变数据类型 比如列表(python2)
c[0] += 1
print(c[0])
print(b)
return inner # 外函数的返回值是内函数的引用
if __name__ == '__main__':
demo = outer(5)
demo() # 6 11
二. Python里的深浅拷贝
1、预备知识一——python的变量及其存储
- python的一切变量都是对象,变量的存储,采用了引用语义的方式,存储的只是一个变量的值所在的内存地址,而不是这个变量的只本身
- 不管多么复杂的数据结构,浅拷贝都只会copy一层。
- 理解:两个人公用一张桌子,只要桌子不变,桌子上的菜发生了变化两个人是共同感受的。
不管多么复杂的数据结构,浅拷贝都只会copy一层:
>>> str1 = 'hello'
>>> str2 = str1
#1、让str1和str2变量都存储了‘hello’所在的内存地址
>>> id(str1)
22748280
>>> id(str1)
22748280
>>>
#2、当str1的值变成‘new hello’后str1的值被重新赋值成'new hello'的内存地址,而str2的值依旧是‘hello’的内存地址
>>> str1 = 'new hello'
>>> id(str1)
22748320
>>> id(str2)
22748280
#3、不管多么复杂的数据结构,浅拷贝都只会copy一层。
>>> sourceList = [1,2,[3,4]]
>>> newList = sourceList
>>> l[2][0]=100
>>> sourceList
[1, 2, [100, 4]]
>>> newList
[1, 2, [100, 4]]
2、copy与deepcopy
- 浅copy: 不管多么复杂的数据结构,浅拷贝都只会copy一层
- deepcopy : 深拷贝会完全复制原变量相关的所有数据,在内存中生成一套完全一样的内容,我们对这两个变量中任意一个修改都不会影响其他变量
浅copy与deepcopy:
import copy
sourceList = [1,2,3,[4,5,6]]
copyList = copy.copy(sourceList)
deepcopyList = copy.deepcopy(sourceList)
sourceList[3][0]=100
print(sourceList) # [1, 2, 3, [100, 5, 6]]
print(copyList) # [1, 2, 3, [100, 5, 6]]
print(deepcopyList) # [1, 2, 3, [4, 5, 6]]
三. Python垃圾回收机制
垃圾回收机制:https://www.cnblogs.com/TM0831/p/10599716.html
简介:
在Python中,垃圾回收机制主要是以引用计数为主要手段,以标记清除和分代回收机制作为辅助手段实现的。
1、引用计数
1. 原理
1)当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1.
2)当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
2. 优点
引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
3. 缺点
1)引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的。
2)这显然比其它那些垃圾收集技术所带来的额外操作只是与待回收的内存数量有关的效率要低。
3)同时,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉。
2、标记-清除
1. 说明
1)它分为两个阶段:第一阶段是标记阶段,GC会把所有的活动对象打上标记,第二阶段是把那些没有标记的对象非活动对象进行回收。
2)对象之间通过引用(指针)连在一起,构成一个有向图
3)从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
注:像是PyIntObject、PyStringObject这些不可变对象是不可能产生循环引用的,因为它们内部不可能持有其它对象的引用。
- 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和
- 第一步将标记块1,并记住块2和3以供稍后处理。
- 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。
- 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。
2、缺点
- 1)标记清除算法作为Python的辅助垃圾收集技术,主要处理的是一些容器对象,比如list、dict、tuple等
因为对于字符串、数值对象是不可能造成循环引用问题。 - 2)清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
3、分代回收
- 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
- Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
- 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
- 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
- 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
- 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
四. 上下文管理(with)
1、什么是with语句
- with是一种上下文管理协议,目的在于从流程图中把 try,except 和finally关键字和资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。
- 所以使用with处理的对象必须有enter()和exit()这两个方法
1)with通过enter方法初始化(enter方法在语句体执行之前进入运行)
2)然后在exit中做善后以及处理异常(exit()方法在语句体执行完毕退出后运行)
2. with语句使用场景
- with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源
- 比如文件使用后自动关闭、线程中锁的自动获取和释放等。
3、with处理文件操作的实例
with open('/etc/passwd') as f:
for line in f:
print(line)
# 这段代码的作用:打开一个文件,如果一切正常,把文件对象赋值给f,然后用迭代器遍历文件中每一行,当完成时,关闭文件;
# 而无论在这段代码的任何地方,如果发生异常,此时文件仍会被关闭。