with管理上下文
- 我们在使用资源文件时,不管使用过程中是否发生异常,都会帮助我们执行清理操作,把资源释放。
- 比如说文件使用后自动管理,线程中锁的自动获取和释放
- 通过
__enter__
方法初始化,然后在__exit__
中做善后以及异常处理 - 使用with处理的对象必须有
__enter__
和__exit__
方法
迭代器
迭代器就是可以记住遍历位置的对象
- 迭代器从第一个元素开始访问,直到所有的元素被全部访问
- 迭代器只能往前不能后退
- 使用
__next__
来遍历 - 调用可迭代对象的
iter
方法就可以得到一个迭代器 - 可迭代对象:列表、元组、字典、集合、range、字符串
- 文件对象本身就是迭代器
生成器
Generator
- 生成器就是可以生成值的函数
- 当一个函数里有了yield关键字就成了生成器
- 生成器可以挂起执行并且保持当前执行的状态
- 生成器是一种特殊的迭代器
生成器协程
- 协程需要使用send(None)或者next(coroutine)来激活才能启动
- 在yield处协程会暂停执行
- 单独的yield value会产出值给调用方
- 可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield表达式
- 协程执行完成后(没有遇到下一个yield语句)会抛出一个Stopiterration错误
装饰器
在不修改原代码的情况下,添加新的功能
- python中一切皆对象,函数也可以当作参数传递
- 装饰器是接受函数作为参数,添加功能后返回一个新函数的函数
- 使用@语法糖使用装饰器
- 类实现装饰器:重写
__call__()
方法
Cpython GIL
Global interpreter Lock
- GIL的功能是:在Cpython解释器中执行的每一个线程,都会先锁住自己,阻止别的线程执行。
- Cpython不能让一个线程一直运行独占解释器,他会轮流运行每个线程,所以cpython的并行是伪并行,其实是用快速的交替运行来模拟并行。
- Cpython解释器的内存管理并不是线程安全的
- 保护多线程情况下对Python对象的访问
- Cpython使用简单的锁机制避免多个线程同时执行字节码
GIL的影响
- 同一时间只能有一个线程执行字节码
- CPU密集程序难以利用多核优势
- IO期间会释放GIL,对IO密集程序影响不大
如何规避GIL影响
区分CPU和IO密集程序
- CPU密集可以使用多进程+进程池
- IO密集使用多线程/协程
- cpython扩展
内存管理
GC
- 引用计数
- 垃圾回收
- 标记清除
- 分代回收
- 内存池机制
引用计数
当我们创建一个对象时,这个对象就通过指针被变量引用,对象刚创建时,引用数为1,python中每个对象都有指向该对象的引用总数–这就是引用计数
查看对象的引用计数:sys.getrefcount()。当把一个对象作为参数传给这个函数时,实际上创建了一个临时的引用。因此,得到的结果比预期值多1.
当一个对象的引用变量名被销毁时,该对象的引用计数减少1
垃圾回收
- 原理
当一个对象的引用数为0时,就说明没有任何变量引用该对象,那么该对象就成为要被回收的垃圾
- 注意
- 垃圾回收的时候,python不能执行其他的任务,
- 只有在特定情况下python才会进行垃圾回收(垃圾少的时候没必要回收)
- 在python运行的时候,会记录分配对象和取消分配对象的次数,当两者差值达到一定阈值的时候,垃圾回收就会启动。
- 阈值分析
> import gc
> gc.get_threshold()
(700,10,10)
700:差值达到700的时候就会启动垃圾回收
# 10:0代回收10次后,会#对1代进行回收一次
# 10:1代回收10次后会启动一次2代回收
标记清除
- 引用计数无法回收循环引用的问题,当AB互相引用而没有被其他对象引用,那么ab都是需要回收的,但是由于ab互相引用,引用计数不为0,就无法被回收。
- 针对循环引用,使用标记清除机制来处理,对象之间通过引用关系连在一起,构成一个有向图,对象是这个有向图的节点,引用关系构成有向图的边。从跟对象出发,沿着有向边遍历对象,可达的对象标记为活动对象,不可达的对象就是等待清除的非活动对象。
- 跟对象就是全局变量,调用栈,寄存器
分代回收
- 分代回收是一种以时间换空间的操作,根据对象的存活时间分为三代,分别的年轻代、中年代、老年代。
- 所有的新建对象都是0代
当某一代经历过垃圾回收仍然存在就会被归入到下一代 - 年轻代的链表总数达到上限时,python的垃圾回收就会启动
内存池
- 对于python内置的对象,都有独立的私有内存池,对象之间的内存池不共享,即int释放的内存,不会分配给float使用
- 大内存使用c的malloc分配,小内存使用内存池分配
缓存机制
当对象的引用计数为0时,就会被销毁并释放内存,其实实际上并不会直接被销毁,因为频繁的创建销毁会让执行效率变低。因为python引入缓存机制
引用计数为0时,把它放入到free_list
链表中,之后创建的对象不会重新开辟内存,而是先从free_list
中找之前的对象并初始化后来使用
- float类型,维护的free_list链表最多可缓存100个float对象
- int类型,不是基于
free_list
,而是一个small_ints
保存小整数,范围-5<=value<=256。 - 维护
unicode_latinnl[256]
链表,内部将所有的ASCII字符缓存起来。针对只含字母、数字、下划线组成的字符串,如果内存中已存在则不会重新创建,而是重复利用。(但是不像free_list
那样一直在内存中存活,只有内存中有才会被重复利用) - list类型,维护的
free_list
数组最多可缓存80个llist对象 - tuple类型,维护
free_list
t数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。 - dict类型,维护的
free_list
最多可容纳80个dict对象
元类
mixin
asyncio
深浅copy
- 浅copy是新开辟一块内存,把所有的对象的内存地址放进来
- 深copy也是先开辟一块内存,针对可变类型和不可变类型分别处理。可变类型:在内存中重新创建一份,不可变类型:使用原来数据的内存地址