8.迭代器、生成器、协程

可迭代对象及检测方法

  • 可迭代对象
    • 可遍历对象就是可迭代对象
    • 列表、元组、字典、字符串都是可迭代对象
    • int数字 和 自定义myclass 默认都是不可以迭代的
    • myclass2 对象所属的类 MyClass2 如果包含了 iter() 方法,此时myclass2 就是一个可迭代对象
    • 可迭代对象的本质:对象所属的类中包含了 iter() 方法
  • 可迭代对象的检测:检测一个对象是否可以迭代,用 isinstance() 函数检测
"""
isinstance(待检测的对象, Iterable)
返回值为:True 可以迭代
         False 不可能迭代
"""
# from collections import Iterable  3.10以上版本取消了以下方法
# ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", "AsyncGenerator", "Hashable", "Iterable", "Iterator", "Generator", "Reversible", "Sized", "Container", "Callable", "Collection", "Set", "MutableSet", "Mapping", "MutableMapping", "MappingView", "KeysView", "ItemsView", "ValuesView", "Sequence", "MutableSequence", "ByteString"]
from collections.abc import Iterable
print(isinstance([1, 2, 3], Iterable))
print(isinstance((1, 2, 3), Iterable))
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable))
print(isinstance("abcdefg", Iterable))
print(isinstance(100, Iterable))   # False

# 类
class MyClass():
    pass
myclass = MyClass()
print(isinstance(myclass, Iterable))   # False

class MyClass2():
    # 增加一个__iter__方法,该方法就是一个迭代器
    def __iter__(self):
        pass
myclass2 = MyClass2()
print(isinstance(myclass2, Iterable))   # True
True
True
True
True
False
False
True

迭代器及其使用方法

  • 迭代器的作用:
    • 记录当前迭代的位置
    • 配合next() 获取可迭代对象的下一个元素值
  • 获取迭代器:iter(可迭代对象)
  • 获取可迭代对象的值:next(迭代器)
  • for循环的本质:
    • 通过 iter(要遍历的对象) 获取要遍历的对象的迭代器
    • next(迭代器)获取下一个元素
    • 帮我们捕获了 StopIteration 异常
  • 自定义迭代器类
"""
1) 一个可迭代对象可以提供一个迭代器
2)可迭代对象--->iter(可迭代对象)  ---> next(迭代器)
               迭代器                下一个元素

迭代器特点:
1)记录遍历的位置
2)提供下一个元素的值(配合next()函数)

for循环的本质:
1)通过 iter(要遍历的对象) 获取要遍历的对象的迭代器
2)next(迭代器)获取下一个元素
3)帮我们捕获了 StopIteration 异常
"""
# 1、data_list1 是一个可迭代对象
data_list1 = [1, 3, 5, 7, 9]
# for value in data_list1:
#     print(value)

# 2、获取迭代器
l1_iterator = iter(data_list1)

# 3、根据迭代器,可以获取下一个元素
value = next(l1_iterator)
print(value)  # 1

value = next(l1_iterator)
print(value)  # 3

value = next(l1_iterator)
print(value)  # 5

value = next(l1_iterator)
print(value)  # 7

value = next(l1_iterator)
print(value)  # 9

value = next(l1_iterator)
print(value)  # 报错 StopIteration

# 自定义迭代器类,满足2点
# 1)必须含有 __iter__()
# 2) 必须含有 __next__()
class MyIterator(object):
    def __iter__(self):
        pass

    # 当 next(迭代器) 的时候,会自动调用该方法
    def __next__(self):
        pass

迭代器应用:自定义列表

# 创建MyList类
class MyList(object):
    # 初始化方法
    def __init__(self):
        # 定义实例属性,保存数据
        self.items = []
    # __iter__()方法,对外提供迭代器
    def __iter__(self):
        # 创建MyListIterator对象
        mylist_iterator = MyListIterator(self.items)
        return mylist_iterator
    # addItem()方法,用来添加数据
    def addItem(self, data):
        self.items.append(data)
        # print(self.items)

# 自定义迭代器类
class MyListIterator(object):
    # 初始化方法
    def __init__(self, items):
        # 定义实例属性,保存传递过来的items
        self.items = items
        # 记录迭代器迭代的位置
        self.current_index = 0
    # 迭代器方法
    def __iter__(self):
        pass
    # 获取下一个元素值的方法
    def __next__(self):
        # 判断当前的下标是否越界
        if self.current_index < len(self.items):
            # 根据下标获取下标对应的元素值
            data = self.items[self.current_index]
            # 下标位置+1
            self.current_index += 1
            # 返回下标对应的数据
            return data
        # 如果越界,直接抛出异常
        else:
            # raise 主动抛出异常 StopIteration 停止迭代
            raise StopIteration

if __name__ == '__main__':
    mylist = MyList()
    mylist.addItem("a")
    mylist.addItem("b")
    mylist.addItem("c")
    for value in mylist:
        print(value)

    my_iterator = iter(mylist)
    value = next(my_iterator)
    print(value)

    value = next(my_iterator)
    print(value)

    value = next(my_iterator)
    print(value)

    value = next(my_iterator)  # StopIteration
    print(value)

# a
# b
# c
# a
# b
# c

迭代器:斐波那契数列

"""
自定义迭代器:
1) 定义迭代器类
2) 类中必须 __iter__() 方法
3) 类中必须 __next__() 方法

目标:
# 指定生成5列的斐波那契数列
fib = Fibnacci(5)
value = next(fib)
print(value)
"""
class Fibnacci(object):

    def __init__(self, num):
        # 定义实例属性,保存生成的列数
        self.num = num

        # 定义变量保存斐波那契数列的第一列和第二列
        self.a = 1
        self.b = 1

        # 记录下标位置的实例属性
        self.current_index = 0

    # __iter__()
    def __iter__(self):
        # 返回自己
        return self

    # __next__(self)
    def __next__(self):
        # 判断列数是否超过生成的总列数
        if self.current_index < self.num:
            # 定义变量,保存a的值 a=b b=a+b 当前列数+1 返回a的值
            data = self.a
            self.a, self.b = self.b, self.a + self.b
            self.current_index += 1
            return data

        # 如果超出范围报错
        else:
            # 主动抛出异常
            raise StopIteration

if __name__ == '__main__':
    for value in Fibnacci(5):
        print(value)

    # 创建迭代器对象
    fib_iterator = Fibnacci(5)
    value = next(fib_iterator)
    print(value)

    value = next(fib_iterator)
    print(value)

    value = next(fib_iterator)
    print(value)
1
1
2
3
5

生成器

生成器是一种特殊的迭代器,按照一定的规律生成数列

"""
生成器是一种特殊的迭代器(保存位置,返回下一个值)
next(迭代器) 得到下一个值
next(生成器) 也能够得到下一个值

生成器创建方式:
1、列表推导式
2、函数中使用了 yield
"""
# 列表推导式
data_list = [x*2 for x in range(10)]
for value in data_list:
    print(value, end=" ")
print()

# 生成器的创建一:
data_list2 = (x*2 for x in range(10))   # 列表推导式的[]换成()
print(data_list2)

# 通过next获取下一个值
value = next(data_list2)
print(value)

value = next(data_list2)
print(value)

print("-----"*10)

def test():
    return 10
m = test()
print("m = ", m)

# 使用yield创建了一个生成器对象
def test1():
    yield 10
n = test1()
print("n = ", n)
value = next(n)
print(value)
0 2 4 6 8 10 12 14 16 18
<genexpr> at 0x00000185C055ECF0>
0
2
m = 10
n = <generator object test1 at 0x00000185BFC90EB0>
10

生成器-案例-斐波那契数列

"""
1、创建一个生成器
    目标:实现斐波那契数列
    1) 定义变量保存第一列和第二列的值
    2) 定义变量保存当前生成的位置
    3) 循环生成数据,条件(当前的列数 < 总列数)
    4) 保存第一列的值 a
    5) 修改 a b 的值(a=b, b = a+b)
    6) 返回 a 的值 yield
2、定义变量保存生成器
next(生成器) 得到下一个元素值
"""
def fibnacci(n):
    a = 1
    b = 1
    current_index = 0
    print("-----探索yield运行机制11111----")
    while current_index < n:
        data = a
        a, b = b, a + b
        current_index += 1

        # 充当return作用
        # 保存程序的运行状态,并且暂停程序执行
        # 当next的时候,可以唤醒程序从yeild位置继续向上执行
        print("-----探索yield运行机制22222----")
        yield data
        print("-----探索yield运行机制33333----")

if __name__ == '__main__':
    fib = fibnacci(5)
    value = next(fib)
    print("---------->", value)
    value = next(fib)
    print("---------->", value)
    value = next(fib)
    print("---------->", value)
    value = next(fib)
    print("---------->", value)
    value = next(fib)
    print("---------->", value)
-----探索yield运行机制11111----
-----探索yield运行机制22222----
----------> 1
-----探索yield运行机制33333----
-----探索yield运行机制22222----
----------> 1
-----探索yield运行机制33333----
-----探索yield运行机制22222----
----------> 2
-----探索yield运行机制33333----
-----探索yield运行机制22222----
----------> 3
-----探索yield运行机制33333----
-----探索yield运行机制22222----
----------> 5

生成器-使用注意

  • return 的作用:可以结束 生成器 的运行
  • send的作用,能唤醒生成器,也能传递参数
  • next(fib) 可以唤醒 生成器,但是不能传递参数

生成器.send(传递给生成器的值)

  • fib.send(1)
  • xxx = yield data # xxx = 1
  • 使⽤send启动⽣成器的时候传⼊的参数必须是None,下次启动⽣成器的时候可以加上参数
def fibnacci(n):
    a = 1
    b = 1
    current_index = 0
    print("-----探索yield运行机制11111----")
    while current_index < n:
        data = a
        a, b = b, a + b
        current_index += 1

        # 充当return作用
        # 保存程序的运行状态,并且暂停程序执行
        # 当next的时候,可以唤醒程序从yeild位置继续向上执行
        print("-----探索yield运行机制22222----")
        xxx = yield data
        print("-----探索yield运行机制33333----")
        # 生成器中能使用return,让生成器结束
        # return "return 能让生成器结束!"
        if xxx == 1:
            return

if __name__ == '__main__':
    fib = fibnacci(5)
    value = next(fib)
    print("---------->", value)
    try:
        value = next(fib)
        print("---------->", value)
        value = fib.send(1)
        print("---------->", value)
        value = next(fib)
        print("---------->", value)
    except Exception as e:
        print(e)
-----探索yield运行机制11111----
-----探索yield运行机制22222----
----------> 1
-----探索yield运行机制33333----
-----探索yield运行机制22222----
----------> 1
-----探索yield运行机制33333----

协程-yield

又称微线程,纤程,从技术的角度来说,“协程就是你可以暂停执行的函数”。

协程的意义:协程只使用一个线程(单线程),在一个线程中规定某个代码块执行顺序

协程的适用场景:当程序中存在大量不需要CPU的操作时(使用IO),适用于协程

# 简单的协程
import time

# 创建work1的生成器
def work1():
    while True:
        print("正在执行work1。。。")
        yield
        time.sleep(0.5)
# 创建work2的生成器
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

  • greenlet 实现协程:greenlet 是一个第三方的模块,自行的调度的微线程
  • 使用步骤:
    • 导入模块:from greenlet import greenlet
    • 创建 greenlet 对象:g1 = greenlet(函数名)
    • 切换任务:g1. switch()
from greenlet import greenlet
import time

def work1():
    while True:
        print("正在执行work1。。。")
        time.sleep(0.5)
        g2.switch()

def work2():
    while True:
        print("正在执行work2。。。")
        time.sleep(0.5)
        g1.switch()

if __name__ == '__main__':
    g1 = greenlet(work1)
    g2 = greenlet(work2)
    # 切换到g1任务
    g1.switch()

协程-gevent

  • gevent 也是第三方库,自动调度协程,自动识别程序中的耗时操作
  • 使用步骤:
    • 导入模块:import gevent
    • 指派任务:g1 = gevent.spawn(函数名, 参数1,参数2,....)
    • join() 让主线程等待协程执行完毕后再退出:g1.join()
  • gevent 不能识别耗时操作的问题
    • 替换time.sleep() ---> gevent.sleep()
    • 打猴子补丁
  • 导入模块 from gevent import monkey
  • 破解所有:monkey.patch_all()

猴子补丁的作用:

  • 在运行时替换方法、属性等
  • 在不修改第三方代码的情况下增加原来不支持的功能
  • 在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加
import gevent
from gevent import monkey

def work1():
    while True:
        print("正在执行work1。。。")
        gevent.sleep(0.5)

def work2():
    while True:
        print("正在执行work2。。。")
        gevent.sleep(0.5)

if __name__ == '__main__':
    g1 = gevent.spawn(work1)
    g2 = gevent.spawn(work2)
    g1.join()
    g2.join()
import time
import gevent
from gevent import monkey
monkey.patch_all()

def work1():
    while True:
        print("正在执行work1。。。")
        time.sleep(0.5)

def work2():
    while True:
        print("正在执行work2。。。")
        time.sleep(0.5)

if __name__ == '__main__':
    g1 = gevent.spawn(work1)
    g2 = gevent.spawn(work2)
    g1.join()
    g2.join()

进程、线程、协程区别

  • 进程资源分配的基本单位、线程CPU调度的基本单位、协程单线程执行多任务
  • 切换效率: 协程 > 线程 > 进程
  • 高效率方式: 进程 + 协程
  • 选择问题:

    • 多进程:
    • 密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。
    • 缺陷:多个进程之间通信成本高,切换开销大。
    • 多线程:
    • 密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。
    • 缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。
    • 协程:
    • 当程序中存在大量不需要CPU的操作时(IO),适用于协程;
    • 缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低,处理网络IO性能比较高。

案例-并发下载器

# 导入模块  urllib.request.urlopen() 打开网址并反回对应的内容(二进制流)
import urllib.request
import gevent

def download_img(img_url, file_name):
    try:
        # 打开网址
        reponse_data = urllib.request.urlopen(img_url)
        # 在本地创建文件,准备保存
        with open(f"./{file_name}", "wb") as file:
            while True:
                # 读取网络资源数据
                file_data = reponse_data.read(1024)
                # 判断读取的数据不为空
                if file_data:
                    # 把读取的内容写入到本地文件中
                    file.write(file_data)
                else:
                    break
    except Exception as e:
        print(f"文件{file_name}下载失败!{e}")
    else:
        print(f"文件{file_name}下载成功!")

def main():
    # 定义图片的下载路径
    img_url1 = "https://v.vimll.com:9999/wp-content/uploads/2020/03/IMG_0192-1620x1080.jpg"
    img_url2 = "https://v.vimll.com:9999/wp-content/uploads/2020/03/IMG_0196-1620x1080.jpg"
    img_url3 = "https://v.vimll.com:9999/wp-content/uploads/2020/03/IMG_0203-1620x1080.jpg"
    # 使用协程调用下载文件的函数 geven.joinall([协程的列表])批量装协程join()
    gevent.joinall([
        gevent.spawn(download_img, img_url1, "1.jpg"),
        gevent.spawn(download_img, img_url2, "2.jpg"),
        gevent.spawn(download_img, img_url3, "3.jpg")
    ])

if __name__ == '__main__':
    main()
文件1.jpg下载成功!
文件2.jpg下载成功!
文件3.jpg下载成功!