.
- Python进阶笔记
- 内容以缩进形式分层
- 有些知识点点到为止
python中一切皆对象
python中一切皆对象
可赋值给一个变量
可以添加到集合对象中
可以作为参数传递给函数
可以当做函数的返回值
type object class
type->class->obj
object是最顶层基类
type也是一个类 同时type也是一个对象
type用于实例一切对象
type()函数用于查看实例由哪一对象生成
.__bases__属性用于查看对象上级继承的是哪一对象
身份 也就是内存地址 用id()函数访问
类型 数据类型
值 赋值
None类型(全局只有一个)
即内存地址相同
数值类型
int float complex bool
迭代类型
序列类型
list bytes byte array memoryview range tuple str array
映射类型
dict
集合类型
set frozenset
上下文管理类型
with语句
其他
模块类型 class和实例 函数类型 方法类型 object对象 type类型 ellipsis类型 notimplemented类对象
魔法函数
魔法函数定义
类中以双下划线开头和结尾的函数 是python内置的
魔法函数影响python语法 python语法实际调用魔法函数
魔法函数
非数学运算
字符串表示
repr 在debug模式调用 输出对象
str 重写字符串 内置函数print()默认调用内置函数str()
数学运算
深入类和对象
鸭子类型和多态
当看到一只鸟走起来像鸭子 游泳起来像鸭子 叫起来像鸭子 那么这只鸟就可以被称为鸭子
方法相关 某个类有某个指定方法 那么就被称为某类型
因为python动态语言的关系 无法从类的类型来判断并实现多态 所以使用鸭子类型来实现多态
抽象基类(abc模块)
hasattr(obj,”fun”) 判断某个对象中是否有某个方法
isinstance(obj,otherobj) 通过继承链 判断某个类是否属于某个类
抽象基类的使用
import abc包
类继承 mataclass=abc.ABCMeta
用@abc.abstractmathod装饰器装饰方法
isinstance和type
type()判断对象id
isinstance() 判断继承继承链
类变量和实例变量
self参数 传递的是类实例
实例变量和类变量是独立的 在实例中修改类变量并不会改变类的类变量 一个类的不同实例会共用同一个实例类变量
类属性和实例属性以及查找顺序
MRO算法
DFS算法 继承关系有菱形时 存在无法达到重写方法的效果
BFS算法 继承关系为Y形时 当出现继承方法重名时 优先继承的类的方法不能优先使用
C3算法(python2.3之后)
使用__mro__属性查看类的查找顺序
静态方法 类方法以及对象方法及参数
方法前由 def 关键字修饰
静态方法用@staticmethod完成初始化
通常在不返回类的情景下使用
没有实例参数self
类方法用@classmethod完成初始化
通常在返回类的情景下使用
第一个参数传递的是类本身 通常写成cls
实例方法
第一个参数传递的是实例本身 通常写为 self
数据封装和私有属性
在对象变量或者对象方法前加双下划线 如 __method 或 __var python自动更名为 _class__method 或者 _class__var 的形式达到隐藏的效果 但仍然可以通过更名后的形式访问
python对象的自省机制
通过__dict__查询属性 会列出部分属性和值 可以通过该属性 以字典是形式访问、添加属性或者修改值
通过dir()方法 只列出对象中的所有属性
super真的是调用父类吗?
super执行顺序
调用的是MRO顺序的下一个类的构造函数
mixing继承模式
mixing类功能单一
不和基类关联 可以和任意基类关联 基类可以不和mixin关联就鞥初始化成功
在mixin中不要使用super方法
python中的with语句
上下文管理器协议
实现两个魔法函数
__enter__() 在with语句中 进入时调用
__exit__() 在with语句中 结束时调用
with…as..语句
with后的语句(通常实例一个对象)执行后调用__enter__()魔法函数 并返回值
__enter__()魔法函数执行后的返回值赋值给as后的变量
as后的变量可以在下边的代码块中调用
语句块执行完后调用 __exit__() 魔法函数
with语句捕获到异常则直接执行__exit__() 魔法函数
contextlib简化上下文管理器
contextlib的使用
import context lib 包
使用@contextlib.contextmanager装饰方法
配合yield使用
yield前的代码块相当于__enter__()魔法函数中的代码块
yield返回一个值
yield后的代码块相当于__exit__() 魔法函数中的代码块
自定义序列类
序列类型的分类
容器序列 (可存放任意类型的容器 )
list tuple deque
扁平序列(指定数据类型)
str bytes byte array array.array
可变序列
list deque byte array array
不可变序列
emspstr tuple bytes
python中序列类型的abc继承关系
在from collection import abc (容器相关的抽象基类)中
Sequence(不可变序列)
继承Reversible 和 Collection
Collection继承Sized Iterable和Container
Sized 实现 len 方法 计算长度
Iterable 实现__iter__ 方法 迭代器 用于for循环
Container 实现 contains 方法 用于使用IN 判断数据是否存在
MutableSequence(可变序列)
必须实现__new__ init getitem setitem delitem len insert() 方法
list中extend方法区别
+运算
只能相加相同的序列类型
返回一个新的实例 也就是说id()不同
+=就地加
实际调用MutableSequence.__iadd__(self,values)方法
iadd 又调用 self.extend(values) 方法
self.extend(values) 通过for循环迭代出每个元素
然后使用self.append()方法追加每个元素
也就是说可以相加不同序列类型
对原来的实例相加 也就是说id()相同
实现可切片的对象(会返回一个新的列表)
可以做取值 赋值 删除操作
[start : end : step]
start 表示切片开始位置 默认为0
end 表示切片截止位置 默认为列表长度
step 表示步长 默认为1
切片需实现的__getitem__(self,item)方法
如果以切片访问 item为slice 即切片对象
如果以索引访问 item为int 即索引位置
bisect维护已排序序列
import bisect 导入bisect包 用来处理已排序的序列
bisect模块
insort方法用于插入默认为insort_right
bisect方法用于查找(使用二分查找) 默认为bisect_right
还有insort_left和bisect_left方法 即当遇到相同元素时 插入以有相同元素的左边
什么时候我们不该使用列表
array和list的一个重要的区别 array只能存放指定的数据类型 其效率高
列表推导式、生成器表达式、字典推导式
列表推导式(列表推导式性能高于列表操作)
[ for in if ] 返回的是一个列表
生成器表达式
( for in if ) 注:返回的是 generator 生成器类
字典推导式
{ key:value for in if } 注: 推导式中的key和value是通常的命名 是不固定的
集合推导式
{ one_value for in if } 只要返回一个值即可生成集合
深入python的set和dict
dict的abc的继承关系
from collection.abc import Mapping
dict属于mapping类型 即继承了mapping
dict的常用方法
clear() 删除字典内所有元素
copy() 浅拷贝 指向同一地址 拷贝后的操作会影响被拷贝的数据 深拷贝需要import copy
fromkeys() 将可迭代是对象转成dict的keys
get() 避免在没有对应元素时抛出异常
items() 拆包key和value
setdefault(key, default=None) 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default
update() 合并或添加 即更新原来的dict #猜想 会把迭代中的奇数位元素作为key 偶数位做为value
dict的子类
不建议继承list和dict等内置的类型 由于底层使用c语言实现 所以一些操作不会调用覆盖后的方法 如果要继承建议使用 from collections import UserDict 模块 完全有python语言编写
from collections import defaultdict
其中的__missing__函数 作用:在找不到元素时 设置默认值
set和fronzenset
set(集合可变) fronzenset(不可变的集合)无序的 不重复 set可以通过add方法添加元素 fronzenset不能
set 的方法
add
update 可将两个set合并为一个set
difference 返回两个集合的差集 #集合运算 | 并集 & 交集 - 差集 运算符实际是调用魔法函数
issubset 判断是否存在交集
dict和set的实现原理
一些结论
dict查找的性能远远大于list
在list中随着list数据的增大 查找时间会增大
在dict中查找元素的时间 不会随着dict的增大而增大
dict的key或者set的值 都
dict原理(与java中的Map相似)
对key做哈希运算 然后做与运算作为偏移量 如果发生冲突则解决冲突 所以查找时间复杂度为O(1)
1.dict的key或者set的值 都是必须是可以hash的
不可变对象 都是可以hash的 如str fronzenset tuple 可自己实现类的__hash__
2.dict的内存开销大 但是查询的速度快 自定义的对象或者python内部的对象都是用dict包装的
3.dict的存储顺序和元素添加顺序有关 和内部的hash表偏移有关 #这就是为什么set在更新数据后print出来的数据位置无序且可能会改变原来数据的位置的原因
4.添加数据有可能改变已有数据的顺序
对象引用、可变性和垃圾回收
python中的变量是什么
python和java中的变量本质不一样 python的变量实质上是一个指针 int str 变量可以看做一个便利贴
可以通过is运算符或者id()函数判断是否是同一变量
==和is的区别
==判断的是内容是否相等 调用的是__eq__魔法函数 #类似java中的Object.equals
is判断的是指针所指的内存地址是否相同 也就是变量id()是否相同
因为python中的intern机制 有时候int str类型内容相同 id也相同 #相当于java中String.intern方法
del语句和垃圾回收
python中垃圾回收的算法是采用 引用计数
引用计数
当一个变量指向一个地址是 那么引用计数器+1
del一个变量时 该地址的引用计数器-1
当引用计数为0时 资源被回收
del语句实际调用__del__魔法函数
一个经典的参数错误
传递的参数尽量为不可变的
引用传递 参数传递
元类编程
property动态属性
@property装饰器
修饰方法后 可以通过 . 的方式访问 变得与访问属性的方式相同
被修饰方法看作一个属性 该属性的值可以自定义逻辑返回
@xxx.setter 装饰器
xxx为被@property装饰器修饰的方法
同理 用来动态设置动态属性
getattr __getattribute__魔法函数
__getattr__在查找不到属性的时候调用
getattribute__所有属性访问的入口 该魔法函数会调用__getattr
属性描述符和属性查找过程
属性描述符
实现__get__ set delete 任何一个魔法方法 就可以将一个类变成属性描述符
实现了__get__ 和 set 称为数据属性描述符
只实现了__get__ 称为非数据属性描述符
使用:在一个类中将属性描述符实例赋值给类变量
属性查找过程
getattr()方法用于访问实例的变量 #如项目里mogodb_client访问collection的三种方法 1.mongodb_client.r20.dmmr18 2.mongodb_clientr20 3.getattr(self.mongodb_client["r20"],"dmmr18")
new 和 init 的区别
new 是用来控制对象的生成过程 ,在对象生成之前
init 是用来完善对象的
如果new方法不返回对象 则不会调用init函数
__new__(cls, args, *kwargs)
如果指定参数名赋值 则赋值给**kwargs 类型为 dict
如果没有指定参数名赋值 则赋值给 *args 类型为 tuple
cls 为对象 需return super.()__new__(cls) 才会调回 init 函数
__init__(self)
在new 之后被调用
自定义元类
元类是创建类的类
type(object_or_name, bases, dict)
第一个参数为类名 传入str类型
第二个参数为 继承基类 当不继承时需要传入一个空tuple ()
第三个参数为 自定义属性和方法 dict类型 key为属性名或函数名 value为属性值或自定义的函数
metaclass 类属性
作用:如 继承抽象基类时做函数重写的检查
指定一个类 通过重写__new__函数来自定义创建类对象和实例的过程
元类需要继承type类
需 return super.()__new__(cos, args, kwargs) args 和 kwargs 也需要完整的传递进来
通过元类实现ORM
迭代器和生成器
python中的迭代协议
迭代器是什么?
迭代器是访问集合内元素的一种方式 一般用来遍历数据
迭代器和以下标的访问方式不一样 迭代器是不能返回的 迭代器提供了一种惰性方式数据的方式
list不是一个迭代器 但是可迭代 for语句 自动使用 iter() 内置方法将list变成一个迭代器 for语句才可迭代
from collection.abc import
Iterable 可迭代 实现__iter__抽象函数
Iterator 迭代器 实现__next__抽象函数
什么是迭代器和可迭代对象
迭代器与迭代对象逻辑实现要分离
迭代器
继承 from collection.abc import Iterator #java中是继承 implements Iterator 接口
实现 __next__魔法函数 维护下一个元素的位置 #java中重写 next() 方法
如没有下一个元素则 raise StopIterator 异常 结束迭代 #java中重写 hasNext() 方法 判断是否还有下一个元素
可迭代对象
实现__iter__ 魔法函数 return 一个迭代器
生成器函数的使用
生成器函数
惰性求值 延迟求值提供了可能
函数中只要有yield关键字 该函数就是生成器函数
yield 返回的是generator(生成器)对象 是可迭代的
return 只能写一个 而yield能写多个
生成器在UserList中的应用
from collection.abc import UserList
UserList 使用python实现
其中__iter__ 魔法函数用到yield关键字 将该魔法函数变为生成器 依次返回下一个迭代元素
python socket编程
弄懂 HTTP、Socket、TCP 这几个概念
socket本质是编程接口 不属于任何协议 对TCP/IP协议的封装 提供给程序猿使用 与下层协议交互 在上层可自定义协议
http 应用层协议
TCP 传输层协议
client和server实现通讯
多线程、多进程和线程池编程
python 中的 GIL
gil global interpreter lock (cpython)
python中一个线程对应于c语言中的一个线程
gil使得同一个时刻只有一个线程在一个cpu上执行字节码, 无法将多个线程映射到多个cpu上执行
gil会根据执行的字节码行数以及时间片释放gil,gil在遇到io的操作时候主动释放
多线程编程 - threading
多线程编程的第一种方式
import threading
threading.Thread(target=func, args=tuple) target 参数传入一个方法 args 参数用于传入方法参数 类型为tuple
获得线程实例后 执行 start()方法
实例的setDaemon(bool) 方法 bool为True时 可将线程设置为守护线程 即主线程结束时同时结束该守护线程
多线程编程的第二种方式
继承 threading.Thread 并实现run函数
实例化类后执行start() 方法即可
join() 方法可以阻塞主线程 等到子线程结束后 才继续执行主线程
线程间通信 - 共享变量和 Queue
共享变量
是线程不安全的
Queue
Queue使用的是deque(双端队列)是线程安全的
一些方法
task_done join qsize empty full put get put_nowait get_nowait
线程同步 - Lock、RLock
from threading import Lock,Rlock
Lock
声明锁 Lock()
lock.acquire 获取锁
lock.release 释放锁
RLock 可重入锁 #相当于java中的ReentrantLock
在同一线程中 acquire和release 操作的次数要相等
线程同步 - condition 使用以及源码分析
from threading import Condition 条件变量 用于复杂的线程间同步
配合with语句使用 进入__enter__ 方法 会对condition加锁
wait 方法执行时 会获取一个锁实例并加锁
并把wait的锁加入双端队列
同时释放condition的锁
notify方法执行时 会对双端队列中的wait锁弹出 并解锁
一些总结
启动顺序很重要
在调用with cond之后才能调用wait或者notify方法
condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放, 上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒
线程同步 - Semaphore 使用以及源码分析
Semaphore 是用于控制进入数量的锁 #相当于java中的Semaphore
from threading import Semaphore 内部使用condition完成
threading.Semaphore(num) num为并发数
acquire方法调用时 其中的计数器会+1 当计数器满时 也就是达到了num的数量时会阻塞
release 方法调用时 其中的计数器会-1 当计数器为零时 会释放锁
ThreadPoolExecutor线程池
一些总结
线程池, 为什么要线程池
主线程中可以获取某一个线程的状态或者某一个任务的状态,以及返回值
当一个线程完成的时候我们主线程能立即知道
futures可以让多线程和多进程编码接口一致
from concurrent.futures import ThreadPoolExecutor
ThreadPoolExecutor(max_workers) max_workers为线程池大小
通过 submit(func, tuple) 函数提交一个方法到线程池中 func为函数 tuple为以元祖的方式给func函数传入参数
submit会立即返回一个futures类 用来监视线程状态
监视线程状态常用方法:done result cancel
from concurrent.futures import as_completed
用来获取线程池中已经完成的线程
使用:传入批量的线程池返回结果 然后通过for语句迭代 如:for future in as_completed(all_task) 最后future.result得到结果
executor.map #executor为一个线程池实例
executor.map(func, *args)
同理 for result in executor.map(func, *args) 迭代直接返回结果
as_completed和executor.map的区别
as_completed 将返回先完成的进程的结果
executor.map 将返回先执行的进程的结果
from concurrent.futures import wait
可以阻塞主线程并可以指定何时继续执行主线程 如:所有线程完成之后才继续往下执行 或者只要有一个线程完成后就继续往下执行
使用:wait(fs) fs为futures序列 还有timeout(等待超时时间)参数 默认为None 和return_when(何时继续执行) 参数 默认为ALL_COMPLETED
ThreadPoolExecutor源码分析
from concurrent.futures import Future
“未来”对象 task的返回容器
在线程池执行submit函数有生成一个Future实例
然后将future实例和需要执行的方法放入一个_WorkItem实例中
_WorkItem又被放到_work_queue队列中 待执行
最后_adjust_thread_count方法控制线程池大小并开启线程 交给_worker方法 _worker函数负责从_work_queue队列中获取_WorkItem并执行run方法
_WorkItem.run()函数负责完成函数的执行和将最后完成的结果赋值给future实例
多线程和多进程对比
多进程使用 from concurrent.futures import ProcessPoolExecutor 包 由于api相同 所以与多线切换很容易 ThreadPoolExecutor换成ProcessPoolExecutor即可
一些总结
耗cpu的操作,用多进程编程, 对于io操作来说, 使用多线程编程,进程切换代价要高于线程
1. 对于耗费cpu的操作,多进程优于多线程
2. 对于io操作来说,多线程优于多进程
由于GIL锁的存在 多进程并行的效率要比多进程并发的效率高
耗费cpu的运算操作应该放到多个cpu上
io操作不怎么耗费cpu 但在切换的操作上 线程效率要略快与进程效率 所以io操作应该用多进程
而且cpu核数是有限的 但进程可以开很多
multiprocessing 多进程编程
os.fork 会新建子进程 只能用于Unix/Linux中 不能用在win中
子进程会继续执行fork函数后的代码 但进程间数据是完全隔离的
from concurrent.futures import ProcessPoolExecutor 底层使用的是multiprocessing
实现多进程方法 需继承 multiprocessing.Process 实现的方法与多线程类似
multiprocessing.Process(target, args)
一些方法 pid start join is_alive
multiprocessing.pool()
也可以实现进程池 如果不指定线程池大小 则内部自动获取os.cpu_count()(cpu核心数)作为进程池大小
pool.apply_async() 异步提交任务 返回ApplyResult 类似Future
pool.close() 关闭进程池 不再接受新任务
pool.join() 等待所有任务完成 需在pool.close() 调用之后调用 不然会报错 Pool is still running
pool.imap() 同对线程池的 executor.map 同样先返回先加入的方法的结果
进程间通信 - Queue、Pipe,Manager
使用的是from multiprocessing import Queue, Manager, Pipe
多进程数据是完全隔离的 共享全局变量不能适用于多进程编程,可以适用于多线程
multiprocessing中的queue不能用于pool进程池
pool中的进程间通信需要使用Manager中的queue 需要实例化 Manager().Queue(num)
Pipe实例化返回receive_pipe(接收管道) send_pipe (发送管道) 只能适用于两个进程通讯 性能要高于Queue
Manager中有很多函数可以实现多进程共享一个内存块 实现类似于多线程共享全局变量 还有很多函数实现通讯同步 API类似于多线程同步API
协程和异步io
并发、并行、同步、异步、阻塞、非阻塞
并发:是指一个时间段内 有几个程序在同一个cpu上运行 但是任意时刻只有一个程序在cpu上运行
并行:是指任意时刻点上 有多个程序同时运行在多个cpu上
同步:是指代码调用IO操作时 必须等待IO操作完成才返回的调用方式
异步:是指代码调用IO操作时 不必等待IO操作完成就返回的调用方式
阻塞:是指调用函数时候当前线程被挂起
非阻塞:是指调用函数时候当前线程不会被挂起 而是立即返回
C10K问题和IO多路复用 (select、poll 和 epoll)
Unix下五种I/O模型
阻塞式I/O 非阻塞式I/O I/O复用 信号驱动式I/O 异步I/O(POSIX的aio_系列函数)
select poll epoll是一个改良的过程
epoll并不代表一定比select好
在并发高的情况下,连接活跃度不是很高, epoll比select
并发性不高,同时连接很活跃, select比epoll好
select+回调+事件循环获取html
使用非阻塞IO完成http请求
client.setblocking(False) 设置非阻塞IO完成http请求 立马返回
但需要不断的while循环 尝试在连接建立后 执行操作
使用select完成http请求 #参考java NIO中的Selector编程
注册
selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
self.client.fileno()为文件描述符
EVENT_WRITE 为事件 辨识可写事件
self.connected 为回调函数 当client连接建立成功时 回调执行
selector.register(self.client.fileno(), EVENT_READ, self.readable)
self.client.fileno()为文件描述符
EVENT_READ 为事件 辨识可读事件
self.readable 为回调函数 当client接收到数据时 回调执行
注销
selector.unregister(key.fd)
key为注册事件标识
key为注册时self.client.fileno()的返回值
回调
事件循环,不停的请求socket的状态并调用对应的回调函数
select本身是不支持register模式
socket状态变化以后的回调是由程序员完成的
selector.select()
返回ready ready返回 key和mask
其中 key.data 返回注册时的回调函数
回调之痛
可读性差
共享状态管理困难
异常处理困难
协程是什么
C10M问题
一些编程问题
回调模式编码复杂度高
同步编码的并发性不高
多线程编程需要线程同步锁
协程
传统函数调用 过程 A->B->C
我们需要一个可以暂停的函数,并且可以在适当的时候恢复该函数的继续执行
出现了协程 -> 有多个入口的函数, 可以暂停的函数, 可以暂停的函数(可以向暂停的地方传入值)
协程需求:暂停 传值 抛异常 关闭
采用同步的方式去编写异步的代码 在适当的时候暂停函数并在适当的时候启动函数
使用单线程去切换任务
线程是由操作系统切换的 单线程切换意味着我们需要程序员自己去调度任务
不在需要锁 并发性高 如果单线程内切换函数 性能远高于线程切换 并发性更高
生成器进阶-send、close和throw方法
生成器可以暂停
生成器不仅可以产出值 还可以接受值
启动生成器有两种 next() 和send()
next() (暂停)
send() (暂停 传值)
方法可以传递值进入生成器内部 同时还可以重启生成器 执行下一个yield位置
用send发送非none值之前,我们必须启动一次生成器, 方式有两种1. gen.send(None), 2. next(gen)
close() (关闭)
会抛出 GeneratorExit 异常 是继承自BaseException
如果捕获后 做pass 处理且后边还有yield语句 close的地方就会抛出 RuntimeError
如果捕获后 raise StopIteration 则不会再抛出异常
所以这个异常不需要去捕获
throw() (抛异常)
给当前yield语句抛出异常
生成器进阶-yield from
from itertools import chain
可以连接可迭代类型 然后在一个for中迭代
yield和yield from的区别
yield只返回原始的结果
yield from 会依次返回可迭代对象中的元素
yield from
main()——>gen():yield from gen_child()
yield from会在调用方(main())与子生成器(gen_child())之间建立一个双向通道
会处理StopIteration等异常以及将结果返回
一些总结
简化版
1. 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法;
2. 如果子生成器支持.throw()和.close()方法,但是在子生成器内部,这两个方法都会抛出异常;
3. 调用方让子生成器自己抛出异常
4. 当调用方使用next()或者.send(None)时,都要在子生成器上调用next()函数,当调用方使用.send()发送非 None 值时,才调用子生成器的.send()方法;
完整版
1. 子生成器生产的值,都是直接传给调用方的;调用方通过.send()发送的值都是直接传递给子生成器的;如果发送的是 None,会调用子生成器的__next__()方法,如果不是 None,会调用子生成器的.send()方法;
2. 子生成器退出的时候,最后的return EXPR,会触发一个StopIteration(EXPR)异常;
3. yield from表达式的值,是子生成器终止时,传递给StopIteration异常的第一个参数;
4. 如果调用的时候出现StopIteration异常,委托生成器会恢复运行,同时其他的异常会向上 "冒泡";
5. 传入委托生成器的异常里,除了GeneratorExit之外,其他的所有异常全部传递给子生成器的.throw()方法;如果调用.throw()的时候出现了StopIteration异常,那么就恢复委托生成器的运行,其他的异常全部向上 "冒泡";
6. 如果在委托生成器上调用.close()或传入GeneratorExit异常,会调用子生成器的.close()方法,没有的话就不调用。如果在调用.close()的时候抛出了异常,那么就向上 "冒泡",否则的话委托生成器会抛出GeneratorExit异常。
async和await
python为了将语义变得更加明确,就引入了async和await关键词用于定义原生的协程
原生协程不能使用next(None)方法预激协程 必须使用send(None)方法
使用async 关键词定义协程方法
await 只接受async关键字定义的协程方法或者使用@types.coroutine装饰器装饰方法(该装饰器在import types包中 包装方法并实现__await__魔法方法)
生成器实现协程
使用import inspect包中inspect.getgeneratorstate()获取生成器状态 如 GEN_CREATED GEN_SUSPENDED GEN_CLOSED
协程的调度依然是 事件循环+协程模式 协程是单线程模式
asyncio并发编程
事件循环
asyncio #参考java中的AIO
包含各种特定系统实现的模块化事件循环
传输和协议抽象
对TCP UDP SSL 子进程 延时调用以及其他的具体支持
模仿futures模块但适用于事件循环使用的Future类
基于yield from 的协议和任务 可以让你用顺序的方式编写并发代码
必须使用一个将产生阻塞IO的调用时 有接口可以把这个事件转移到线程池
模仿threading模块中的同步原语 可以用在单线程内的协程之间
一些总结
事件循环+回调(驱动生成器)+epoll(IO多路复用)
asyncio是python用于解决异步io编程的一整套解决方案
tornado、gevent、twisted(scrapy, django channels)
torando(实现web服务器), django+flask(uwsgi, gunicorn+nginx)
tornado可以直接部署, nginx+tornado
一些使用
在协程编码中不能使用非异步阻塞方法
loop=asyncio.get_event_loop() 异步事件循环 可以看做协程池
loop.run_until_complete() 阻塞方法 直到协程方法执行完之后才往下执行 可以接收协程类型 协程的future类型 协程的task 类型 #相当于多线程中的join()
asyncio.wait() 接收可迭代异步对象
asyncio.sleep() 异步IO的sleep 需在前边加await关键字
获取协程的返回值
asyncio.ensure_future() 内部使用loop.create_task将协程包装成task
task.add_done_callback() 为协程添加回调方法 future会作为参数传入回调函数中
from functools import partial 偏函数 可以包装函数 重新定义函数签名
asyncio.wait 相当于多线程中的wait 可以指定何时结束
asyncio.gather 与wait用法类似 但可以将协程分组 等待组完成后结束
task取消和子协程调用原理
task取消
run_until_complete 内部使用run_forever() 但为future添加了个一个回调函数future.add_done_callback(_run_until_complete_cb) 当future完成时调用_run_until_complete_cb(fut) 在其中执行fut._loop.stop() 将事件循环停止 即将事件停止标识为True self._stopping = True
loop会被放到future中
asyncio.Task.all_tasks() 从loop中获取所有task
task.cancel() 取消task 返回True取消成功 返回False取消失败
loop.stop() 停止loop
loop.run_forever() 重新运行loop
loop.close() 最后关闭loop
子协程调用
#参考
与yield from相同
call_soon、call_at、call_later、call_soon_threadsafe
loop.call_soon() 将回调函数加入FIFO队列 并按照注册顺序尽可能的回调返回
loop.call_at 在loop某一时间点回调 用到loop.time()
loop.call_later() 在指定时间后回调
loop.call_soon_threadsafe() 是loop.call_soon() 的线程安全版
ThreadPollExecutor 和 asycio 完成阻塞 IO 请求
loop.run_in_executor(executor, func, args ) executor为线程池实例 func为放入线程池中的函数 args为func参数 返回task 然后交给loop运行
asyncio 模拟 http 请求
await asyncio.open_connection(host,port) 语句 等待建立连接
返回 reader 和writer reader用于接收响应 writer用于发送请求
async for 语法 异步for循环 调用__anext__魔法函数
asyncio.as_completed(tasks) 返回一个由@coroutine修饰的 _wait_for_one() 协程 其中才返回 f.result() 所以for得到的task前需要要加await 等待返回结果 #与多线程类似
future 和 task
future
由于协程的单线程的原因 获取结果时执行_schedule_callbacks(self) 其中通过for循环获取 self._callbacks列表中的callback 然后执行self._loop.call_soon(callback, self)
task
Task是Future的子类 是协程和Future之间的桥梁
task在__init__时调用__step通过send(None)预激了协程
task捕获在协程最后return时抛出的StopIteration异常 并通过self.set_result(exc.value)设置返回结果
asyncio同步和通信
from asyncio import Lock, Queue
使用:await lock.acquire() 或 with await lock 或 async with lock
with await lock 或 async with lock 分别执行的是__await__和@coroutine修饰的__aenter__魔法函数
参考
版权属于:羽子
本文链接:https://reki.me/studying/python3-advanced.html
本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
允许自由转载和修改,但请务必标明文章来源且不得运用于商业目的并以相同方式分享。