控制流程
- 迭代器
- 生成器
- 上下文管理器
- 进程、线程
- 协程
- future处理并发
- asyncio处理并发
- python socket编程
本章节我会从 http、socket、tcp 协议开始讲起,通过 socket 方式实现客户端和服务端让大家名明白聊天类软件的核心、要想深刻理解 web 编程、我们必须知道 socket 编程,本章节我们将通过多线程+ socket 的方式实现支持并发的服务端、最后通过 socket 模拟 http 的请求来实现为后续的异步 IO 打下并发的基… - 多线程、多进程和线程池编程
多线程、多进程编程一直是面试中被问到的高频问题,本章节我们将从 GIL 开始讲解多线程以及多进程的应用场景、之后详细的介绍多线程的编码、线程间通信以及线程的同步- Lock\Rlock\Condition,通过对 condition 的源码分析加深大家对条件变量的理解,接着通过线程池 ThreadPoolExecutor 的使用和源码分析加深大家对… - 协程和异步io
本章节是一个过渡章节,也是从生成器过渡到协程的最重要的章节,本章节我们将从阻塞和非阻塞等概念开始一直到引出多线程和多进程编程在并发编程中的不足、IO多路复用,然后我们会通过事件循环+回调的方式完成高并发的请求,之后我们会讲解回调之痛以及生成器进阶中的 send、close 和 yield from 等功能,最后通过这… - asyncio并发编程
asyncio 作为 python 未来最有野心也是最有前景的模块,是我们学习 python 高并发编程的必学模块。有了12章的基础,我们直接使用 asyncio 来进行并发编程就会变得容易理解,我们从 asyncio 的基本功能开始讲解、如何将任务提交到asyncio、如何将 ThreadPoolExecutor 和 asyncio 集成,明白 asyncio 内部是如…
迭代器
- 一种惰性获取数据项的方式,即按需一次获取一个数据项,这就是迭代器的模式(Iterator pattern)
- 所有的生成器都是迭代器,因为生成器完全实现了迭代器接口
迭代器:是访问集合元素的一种方式,迭代器从对象集合的第一元素开始且只能向后访问,直到所有元素访问结束。迭代器适合用于遍历巨大或无限的集合。
特点:
- 访问者不需要关系迭代器内部的结构,仅需要通过next()方法不断的去取下一个内容。
- 不能随机访问元素,只能从头到尾依次访问
- 访问一半时不能往回退
- 便于循环比较大的数据集合,节省内存。
生成迭代器:a = iter([1,2,3,4,]) a.next()
支持范围
- for循环,可迭代对象
- 构建和扩展集合的类型
- 逐行遍历文本文件
- 列表推导式、字典推导式和集合推导式
- 元组拆包
- 调用函数时,使用*
拆包实参
# Sentence类第一版本,单词序列
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __getitem__(self, index):
return self.words[index]
def __len__(self):
return len(self.words)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
s = Sentence('"The time has come,",the walrus said,')
print(s)
print('单个输出:',s[2])
for word in s:
print(word)
list(s)
Sentence('"The time ha... walrus said,')
单个输出: has
The
time
has
come
the
walrus
said
['The', 'time', 'has', 'come', 'the', 'walrus', 'said']
序列可迭代的原因
解释器迭代对象x时,会调用iter(x)
iter内置函数作用:
- 检查对象是否实现了iter方法,如果实现了就调用它,获取一个迭代器。
- 如果没有实现iter方法,但是实现了getiterm方法,python会创建一个迭代器,尝试按顺序获取元素。
- 尝试失败,抛出TypeError异常
任何python序列都可迭代的原因是,都是实现了getitem方法,标准的序列也都实现了iter方法
可迭代对象与迭代器
使用iter内置函数可以获取迭代器的对象。
对象可迭代的两种情况:
- 对象实现了能返回迭代器的iter方法
- 序列都可迭代;实现了getitem方法,而且其参数从零开始的索引。
迭代器
迭代器实现了无参数的next方法,返回序列的下一个元素;如果没有元素了,那么抛出StopIteration异常。
#典型的迭代器
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words)
# 定义迭代器类
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
'''
可迭代的对象有个__iter__方法,每次都实例化一个新的迭代器;
而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身。
'''
' \n可迭代的对象有个__iter__方法,每次都实例化一个新的迭代器;\n而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身。\n'
生成器
生成器:如果函数中包含yield语法,那这函数就会变成生成器(generator)。调用生成器函数时,会返回一个生成器对象。
函数生成器返回迭代器,再用next()返回元素。 调用next()函数,程序执行到yield 输入其后的值,
然后再次调用next()时,才会执行yield之后的程序。 (在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行)
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
yield word
return
def gen_123():
yield 1
yield 2
yield 3
for i in gen_123(): # 这里生成器也是一种迭代器
print(i)
next(gen_123())
1
2
3
1
生成器表达式
生成器表达式可以理解为惰性的列表推导,不会迫切的构建列表,而是返回一个生成器。使用()表达。
when:
生成器表达式是创建生成器的简洁语法,这样无需先定义函数在调用。
生成器函数 较灵活,可多语句实现复杂逻辑,也可以做协程使用。
def gen_AB(): # 直接执行 输出print() 里的内容
print('start')
yield 'A'
print('continue')
yield 'B'
print('end.')
res1 = [x * 3 for x in gen_AB()]
print('列表:', res1)
for i in res1:
print('list for循环:', i)
res2 = (x * 3 for x in gen_AB())
print('生成器:',res2) # 这个直接输出生成器的地址
for i in res2:
print('生成器的循环:', i)
start
continue
end.
列表: ['AAA', 'BBB']
list for循环: AAA
list for循环: BBB
生成器: <generator object <genexpr> at 0x0000005853E73D00>
start
生成器的循环: AAA
continue
生成器的循环: BBB
end.
把迭代器当成协程
.send()可以致使生成器前进到下一个yield表达式中。
生成器用于生成供迭代的数据
协程是数据的消费者
虽然协程出现了yield产出值,但这与迭代无关
# 裴波纳契数列生成器
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
上下文管理器
这部分讨论一些流程控制
- with语句和上下文管理器
- for、while和try语句的else子句
with语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文。
这样能避免错误并减少样板代码,因此API更安全易用。with除了自动关闭文件外,还有很多用处。
for、while和try语句的else子句,在所有的情况下,如果异常或者return、break或continue语句导致控制权跳到复合语句的主块之外,else子句也会被跳过。(其实else用then表示更容易理解,但这添加了新关键字,属语言重大变化!)
上下文管理器–管理-〉with
迭代器–管理-〉for
with 语句简化了try/finally模式,保证执行完一段代码后,即使出现异常、return或sys.exit()调用而中止,也会执行指定的操作。
上下文管理器协议包含两个方法:
1. enter
2. exit
with语句开始运行时,会在上下文管理器对象上调用enter方法,with运行结束后,在上下文管理器对象上调用exit方法。
# with open('mirror.py') as fp: # 执行with后面的表达式得到的结果是上下文管理器对象
# src = fp.read(60)
from mirror import LookingGlass
with LookingGlass() as what: # 上下文管理器是LookingGlass类的实例,python在上下文管理器上调用__enter__方法,把返回结果绑定在what上
print('Alice ,Kitty and Snowdrop')
print(what)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-3-321a12f2a48b> in <module>()
1 # with open('mirror.py') as fp: # 执行with后面的表达式得到的结果是上下文管理器对象
2 # src = fp.read(60)
----> 3 from mirror import LookingGlass
4 with LookingGlass() as what:
5 print('Alice ,Kitty and Snowdrop')
ModuleNotFoundError: No module named 'mirror'
contextlib模块中实用工具
closing
如果对象提供了close()方法,没有实现enter/exit协议,那么可以使用这个函数进行构造上下文管理器。suppress
构建临时忽略指定异常的上下文管理器。@contextmanager
这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了。ContextDecorator
这是个基类,用于定义基于类的上下文管理器,这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。ExitStack
这个上下文管理器能进入到多个上下文管理器。ExitStack按照后进先出的顺序调用栈中各个上下文管理的exit方法。
@contextmanager
以上工具使用最广泛的是@contextmanager,其中迷惑人的一点是它与迭代无关,却要使用yield。
这个装饰器减少创建上下文管理器的样本代码量,因为不用编写一个类,定义enter和exit方法。
只需要 实现yield 的生成器,生成想让 enter 方法返回的值。
'''
上下文管理器 类的实现
'''
class LookingGlass:
def __enter__(self):
import sys
self.original_write = sys.stdout.write
sys.stdout.write = self.reverse_write
return 'JABBERWOCKY'
def revere_write(self,text):
self.original_write(text[::-1])
def __exit__(self,exc_type,exc_value,traceback):
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('please do not divide by zero')
return True
'''
@contextmanager装饰的生成器中,从yield前后函数分为两部分:
1.yield语句前所有的代码在with块开始时(即解释器调用__enter__方法时)执行;
2.yield语句后所有的代码在with块结束时(即解释器调用__exit__方法时)执行。
'''
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
yield 'JABBERWOCKY'
sys.stdout.write = original_write
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
msg = ''
try:
yield 'JABBERWOCKY'
except ZeroDivisionError:
msg = 'Please do not divide by zero'
finally:
sys.stdout.write = original_write
if msg:
print(msg)
协程
- 生成器 过程:yield item 会产出一个值,提供给next()调用输出,此外还会暂停生成器,直到下一次调用next()输出值。
- 协程 虽然有yield 但是可以产出值也可以不产出。可能会从.send(datum)方法接收数据,而不是next()。
'''
用作协程的生成器的基本行为
'''
def simple_coroutine():
print('-> coroutine started')
x = yield # yield 表达式从客户接收数据,产出的将是None (本文中使隐式的表达式)
print('-> coroutine received:',x)
my_coro = simple_coroutine()
# my_coro
next(my_coro) # 先激活协程,再使用.send()
my_coro.send(42)
####
# 协程有四个状态:
# GEN_CREATED 等待开始执行
# GEN_RUNNING 解释器正在执行
# GEN_SUSPENDED 在yield表达式处暂停
# GEN_CLOSED 执行结束
####
-> coroutine started
-> coroutine received: 42
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-15-56999bc8f048> in <module>()
10 # my_coro
11 next(my_coro) # 先激活协程,再使用.send()
---> 12 my_coro.send(42)
13
14 ####
StopIteration:
def averate():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
coro_avg = averate() # 创建协程对象
next(coro_avg) # 调用next()函数,预激协程
coro_avg.send(10) # 调用.send()方法,产出平均值
coro_avg.send(56)
33.0
'''
协程需要预激,不预激,那么协程就没有什么用了;
前文用 next() 预激,有时候会用 预激装饰器
'''
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args,**kwargs):
gen = func(*args,**kwargs)
next(gen) #预激生成器
return gen # 返回生成器
return primer
@coroutine
def averate():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
aa = averate()
aa.send(23)
aa.send(44)
33.5
协程 的终止与异常处理
throw、close
协程中未处理的异常,会向上传给next() 或send()。
显示地发送异常给协程,两个方法:throw 和 close。
- generator.throw
- generator.close
yield from
yield from x表达式对x对象所做的第一件事是,调用iter(x),从中获取迭代器。x可以是任何可迭代的对象。
def gen():
for c in 'AB':
yield c
for i in range(1,3):
yield i
print(list(gen()))
def gen_yield():
yield from 'AB'
yield from range(1,3)
list(gen_yield())
['A', 'B', 1, 2]
['A', 'B', 1, 2]
'''
委派生成器:包含yield from <iterable>表达式的生成器函数
子生成器:从yield from表达式中<iterable>部分获取的生成器
调用方:指调用委派生生成器的客户端代码。
'''
from collections import namedtuple
Result = namedtuple('Result','count averae')
# 子生成器
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total/count
return Result(count,average)
#委派生成器
def grouper(results,key):
while True:
results[key] = yield from averager()
# 客户端代码,即调用方
def main(data):
results = {}
for key, values in data.items():
group = grouper(results, key)
next(group)
for value in values:
group.send(value)
group.send(None)
report(results)
# 输出报告
def report(results):
for key, result in sorted(results.items()):
group, unit = key.split(';')
print('{:2}{:5}averaging {:.2f}{}'.format(
result.count, group,result.average,unit))
data = {
'girls;kg':
[40.9,38.5,44.3,42.2,45.2,41.7,44.5,38.0,40.6,44.5],
'girls;m':
[1.6,1.51,1.4,1.3,1.41,1.39,1.33,1.46,1.45,1.43],
'boys;kg':
[39.0,40.8,43.2,40.8,43.1,38.6,41.4,40.6,36.3],
'boys;m':
[1.38,1.5,1.32,1.25,1.35,1.48,1.25,1.49,1.46],
}
if __name__ == '__main__':
main(data)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-24-86999b5ec675> in <module>()
57
58 if __name__ == '__main__':
---> 59 main(data)
<ipython-input-24-86999b5ec675> in main(data)
36 group.send(None)
37
---> 38 report(results)
39
40 # 输出报告
<ipython-input-24-86999b5ec675> in report(results)
43 group, unit = key.split(';')
44 print('{:2}{:5}averaging {:.2f}{}'.format(
---> 45 result.count, group,result.average,unit))
46
47 data = {
AttributeError: 'Result' object has no attribute 'average'
future处理并发
future指一种对象,表示异步执行的操作,是concurrent.futures模块和asyncio包的基础,高效处理网络I/O,需要使用并发,因为网络有很高的延迟,所以为了不浪费CPU等待时间,最好在收到网络响应之前做些其他的事。
# 下载脚本
import os
import time
import sys
import requests
POP20_CC=('CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'
DEST_DIR = 'downloads/'
def save_flag(img, filename):
path = os.path.join(DEST_DIR,filename)
with open(path,'wb') as fp:
fp.write(img)
def get_flag(cc):
url = '{}/{cc}/{cc}.gif'.format(BASE_URL,cc=cc.lower())
resp = requests.get(url)
return resp.content
def show(text):
print(text, end='')
sys.stdout.flush()
def download_many(cc_list):
for cc in sorted(cc_list):
image = get_flag(cc)
show(cc)
save_flag(image,cc.lower()+'.gif')
return len(cc_list)
def main(download_many):
t0 = time.time()
count = download_many(POP20_CC)
elapsed = time.time() - t0
msg = '\n{} flags downloaded in {:.2f}s'
print(msg.format(count,elapsed))
if __name__ =='__main__':
main(download_many)
BD
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-2-5fc035ef642f> in <module>()
41
42 if __name__ =='__main__':
---> 43 main(download_many)
<ipython-input-2-5fc035ef642f> in main(download_many)
35 def main(download_many):
36 t0 = time.time()
---> 37 count = download_many(POP20_CC)
38 elapsed = time.time() - t0
39 msg = '\n{} flags downloaded in {:.2f}s'
<ipython-input-2-5fc035ef642f> in download_many(cc_list)
30 image = get_flag(cc)
31 show(cc)
---> 32 save_flag(image,cc.lower()+'.gif')
33 return len(cc_list)
34
<ipython-input-2-5fc035ef642f> in save_flag(img, filename)
14 def save_flag(img, filename):
15 path = os.path.join(DEST_DIR,filename)
---> 16 with open(path,'wb') as fp:
17 fp.write(img)
18
FileNotFoundError: [Errno 2] No such file or directory: 'downloads/bd.gif'
# concurrent.futures模块下载
from concurrent import futures
from flags import save_flag, get_flag, show, main
MAX_WORKERS = 20
def download_one(cc):
image = get_flag(cc)
show(cc)
save_flag(image, cc.lower() + '.gif')
return cc
def download_many(cc_list):
workers = min(MAX_WORKERS, len(cc_list))
with futures.ThreadPoolExecutor(workers) as executor:
res = executor.map(download_one, sorted(cc_list)) # map返回一个生成器
return len(list(res))
if __name__ == '__main__':
main(download_many)
'''
future封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果后可以获取结果
'''
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
<ipython-input-3-2395391c8e06> in <module>()
2 from concurrent import futures
3
----> 4 from flags import save_flag, get_flag, show, main
5
6 MAX_WORKERS = 20
ModuleNotFoundError: No module named 'flags'
阻塞型I/O和GIL
CPython解释器本身是线程不安全的,因此有了全局解释器锁GIL,一次只允许使用一个线程执行python字节码,因此python线程通常不能同时使用多个CPU核心。
对于I/O密集型应用,python线程还能发挥作用。
concurrent.futures模块
在CPU密集型作业中使用concurrent.futures模块能轻松绕开GIL。ProcessPoolExecutor类把工作分给多个python进程处理,因此如果需要做CPU密集型处理,使用这个模块绕开GIL,利用多个CPU核心。