通过迭代器不断取出可迭代对象中的下一个元素的值 的过程
for
能能被迭代的对象
from collections import Utterable
isinstance(对象, Iterable)
实现 __iter__提供迭代器
from collections import Iterable
data = [1,2,3,4,5]
for i in data:
print(i)
# 1 通过一个记录位置的对象 不断地间接访问 对象中数据的过程 - 迭代
# 2 可迭代对象 - 可以被迭代的对象 for循环
# 3 如何判断一个对象是否是可迭代类型的对象 isinstance(对象, 类型)
# 常见的容器类型 就是典型的可迭代类型
print("判断data是否是列表类型", isinstance(data, list))
# 4 自定义可迭代类型 实现类中 __iter__方法 的类就是可迭代类型
class MyRange(object):
def __init__(self, n):
self.n = n
def __iter__(self):
"""实现一个可迭代类型 一定要实现本方法 返回一个迭代器"""
pass
print("判断MyRange是否是可迭代类型", isinstance(MyRange(5), Iterable))
for i in MyRange(5):
print(i)
print("判断data是否是可迭代类型", isinstance(data, Iterable))
print("判断整数是否是可迭代类型", isinstance(123, Iterable))
print("判断元组是否是可迭代类型", isinstance((1,2,3), Iterable))
print("判断字符串是否是可迭代类型", isinstance("123456", Iterable))
print("判断字符串是否是可迭代类型", isinstance({1:2}, Iterable))
迭代的访问 可迭代对象中的下一个元素
应用场景: 用户只需要关心如何使用迭代器访问数据 而不需要关键数据该如何访问
解耦合
每一种可迭代对象都会提供对应的迭代器
from collections import Iterator
isinstance(对象,Iterator)
实现
__next__提供下一个元素的值
__iter__返回自身即可 应对情况 迭代器 = iter(迭代器)
from collections import Iterator,Iterable
# 1 迭代器Iterator- 记录每次访问的位置信息的对象 便于下一次访问 (当访问完成后会自动执行下一个位置)
# 2 应用场景 不同可迭代对象有不同的访问方式 每一种可迭代对象都会提供一个迭代器
# 用户只需要通过迭代器取出 下一个元素 的值即可 而不用具体访问细节 - 解耦合
# 3 自定义迭代器类型 实现__next__方法
class MyRangeIter(object):
"""迭代器类"""
def __init__(self, n):
self.n = n
self.i = 0
def __next__(self):
"""实现迭代器类型一定要实现该方法 目的:返回用户需要的下一个元素的值"""
if self.i < self.n:
self.i += 1
return self.i
else:
# 停止 迭代 当迭代器迭代完成的时候 抛出这个异常
raise StopIteration
def __iter__(self):
"""Python规定迭代器 必须是可迭代对象 因此也必须使用该方法"""
return self
class MyRange(object):
"""可迭代类型"""
def __init__(self, n):
self.n = n
def __iter__(self):
"""实现一个可迭代类型 一定要实现本方法 目的: 返回一个迭代器"""
return MyRangeIter(self.n)
# 4 判断迭代器类型
print("判断迭代器类型",isinstance(MyRangeIter(5), Iterator))
print("判断迭代器是否是可迭代对象",isinstance(MyRangeIter(5), Iterable))
print("判断可迭代对象是否是迭代器",isinstance(MyRange(5), Iterator))
for i in MyRange(5):
print(i)
input(":")
iter函数 迭代器 = iter(可迭代对象)
取出可迭代对象中提供的迭代器
next函数 下一个元素的值 = next(迭代器)
通过迭代器取出下一个元素的值
是一种特殊的迭代器, 支持迭代器所有的操作
列表推导式[] ——> ()
优点: 节约资源, 不用占用大量空间
在用户需要访问数据的时候 才延迟产生
含有yield关键字的函数 不再是普通函数 而是生成器函数
def myrange(n):
start = 0
while start < n:
# 1 执行到yield会暂停当前代码执行 将后面的值返回到调用生成器代码的地方
# 2 当前生成器代码再次执行时 直接从上次暂停的地方继续往下执行
yield start
return 1000 # 正常情况下 当前生成器会执行3次才会停止迭代 而有return关键字就在第二次停止迭代了
start += 1
# 生成器函数不是普通函数 调用 生成器函数() 产生生成器对象
# 执行生成器函数的代码 需要调用next(生成器对象)
# yield关键字的作用
gen = myrange(3)
i = next(gen)
print(i)
try:
i = next(gen)
print(i)
except StopIteration as e:
# 如果需要获取到 生成器最终return的值 需要捕获异常
print("接收到了异常数据 %s" % e)
pass
# i = next(gen)
# print(i)
# print(next(gen)) # 迭代完成 StopIteration
#
执行生成器并且传参:
def myrange(n):
start = 0
while start < n:
# 1 执行到yield会暂停当前代码执行 将后面的值返回到调用生成器代码的地方
# 2 当前生成器代码再次执行时 直接从上次暂停的地方继续往下执行
number = yield start
print("接收到了数据%s" % number)
start += 1
if __name__ == '__main__':
gen = myrange(3)
# 生成器支持操作-next函数 迭代器支持的操作
i = next(gen)
print(i)
# 生成器支持操作-send方法 特有
i = next(gen)
print(i)
i = gen.send(100)
print(i)
# 对比 两种执行生成器的方式的异同
# 1 同: 都可以执行生成器代码 并且获取到一个值
# 2 异: next不可以发送数据; send可以发送数据;
# 第一次调用时send(None), next没有这个要求
1 执行到yield会暂停当前代码执行 将后面的值返回到调用生成器代码的地方
2 当前生成器代码再次执行时 直接从上次暂停的地方继续往下执行
协程是 用户层面的多任务调度机制 数量可以几十万
进程线程是操作系统层面的多任务机制 数量受操作系统的限制
了解greenlet / yield使用协程
掌握gevent模块的使用
使用yield实现协程:
import time
def work1():
while True:
print("----work1---")
yield
time.sleep(0.5)
def work2():
while True:
print("----work2---")
yield
time.sleep(0.5)
if __name__ == '__main__':
# 创建生成器对象
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
使用greenlet实现多任务切换:
import time
import greenlet # py3环境
def work1():
while True:
print("----work1---")
g2.switch()
time.sleep(0.5)
def work2():
while True:
print("----work2---")
g1.switch()
time.sleep(0.5)
if __name__ == '__main__':
# 创建协程
g1 = greenlet.greenlet(work1)
g2 = greenlet.greenlet(work2)
# 切换到第一个协程执行 手动切换
g1.switch()
创建启动协程 协程对象 = gevent.spawn(入口, 参数列表)
等待协程完成 协程对象.join()
等待多个协程完成 gevent.joinall()
自动切换 from gevent import monkey; monkey.patch_all()
from gevent import monkey
monkey.patch_all() # 破解代码 实现自动切换 默认不能自动切换
import gevent
import time
# 能够切换的操作 time.sleep(1) recv accept 将默认阻塞的操作变为非阻塞的操作
def worker(no):
"""协程函数"""
for i in range(3):
print("这是协程 %s %s" % (no, gevent.getcurrent()))
time.sleep(1) # gevent.sleep(1)
def main():
# 创建协程 自动运行
g1 = gevent.spawn(worker, 1111)
g2 = gevent.spawn(worker, 2222)
# 阻塞等待协程执行完成 - 主要目的是需要让主进程主线程阻塞在这里
# g1.join()
# g2.join()
gevent.joinall([g1, g2])
if __name__ == '__main__':
main()
资源消耗不关心 要求稳定 用户多进程
资源消耗关心 多线程或者协程
多任务的网络程序 建议优先使用协程
from gevent import monkey
monkey.patch_all() # 启动切换
import gevent
import urllib.request
import time
def down_img(url):
"""下载指定路径的图片 参数就是图片的链接地址-网址 URL URI"""
# 1 从URL中解析出文件名称
# https://rpic.douyucdn.cn/live-cover/roomCover/2018/08/15/a0c1206ae5da0c02a677506af9f41c40_big.jpg
file_name = url[url.rfind("/") + 1: ]
print("开始下载图片 %s" % file_name)
# 2 响应对象<网页数据> 请求 打开网址获取资源对象
response = urllib.request.urlopen(url)
# 3 解析出其中数据 bytes
data = response.read()
# 4 写入文件
with open(file_name, "wb") as file:
file.write(data)
print("完成下载图片 %s" % file_name)
def main():
begin = time.time()
# 1 创建协程
img_list = [
"https://rpic.douyucdn.cn/live-cover/roomCover/2018/08/15/a0c1206ae5da0c02a677506af9f41c40_big.jpg",
"https://rpic.douyucdn.cn/live-cover/roomCover/2018/10/10/2694864718ef0772c36712cd0e12e3a7_big.jpg",
"https://rpic.douyucdn.cn/live-cover/roomCover/2018/10/29/b28ff3c2352b21c766b823db35efc767_big.jpg"]
g1 = gevent.spawn(down_img, img_list[0])
g2 = gevent.spawn(down_img, img_list[1])
g3 = gevent.spawn(down_img, img_list[2])
# 2 等待协程执行完成
gevent.joinall([g1, g2, g3])
end = time.time()
print("花费了%f秒" % (end-begin))
if __name__ == '__main__':
main()