迭代器

迭代是访问元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有元素被访问结束。迭代器只能往前不能后退

  • 可迭代对象
    如果想要一个对象称为可迭代的对象,那么必须实现__Iter__方法

  • 迭代器
    如果想要一个对象称为可迭代的对象,那么必须实现__Iter__方法和__next__方法

for循环,list,dict,tuple的本质就是迭代器循环:

  1. 先调用对象的__iter__方法,将其变成一个迭代器对象
  2. 调用__next__(迭代器),将得到的返回值赋值给变量名
  3. 循环往复直到__next__(迭代器)抛出异常,for会自动捕捉异常然后结束循环

例如:

from collections.abc import Iterator
from collections.abc import Iterable


class Animals:
    def __init__(self):
        self.animal = []
        self.num = 0

    def add(self, name):
        self.animal.append(name)

    def __iter__(self):
        return self

    def __next__(self):
        if self.num < len(self.animal):
            result = self.animal[self.num]
            self.num += 1
            return result
        else:
            raise StopIteration


animals = Animals()
animals.add('dog')
animals.add('cat')
animals.add('fish')

print(isinstance(animals, Iterable))  # 确认该类是不是可迭代对象
print(isinstance(animals, Iterator))  # 确认该类是不是迭代器

for i in animals:
    print(i)

这里定义的Animals就是一个迭代器,它可以使用for循环进行迭代输出

迭代器的好处

替代器可以是一种生成列表的方法,而不会占用大量的内存空间去存放数据,而是生成这种数据的方法。
例如生成斐波那契数组,我们没必要把大量的数组放在一个列表,而是定义一个生成斐波那契数组的方法,使用的时候就可以直接迭代调用

还有在python2中的range()方法,它是直接生成了列表,在python3中的range()方法是一个迭代器,不会直接生成对应的列表,有效解决了数组占用大量空间的问题


生成器

  • 生成器是一类特殊的迭代器

如果一个函数里有yield语句,那么这个就不是函数了,而是一个生成器的模板,当去调用这个的时候,发现这个函数中有yield,那么此时就不是在调用函数,而是创建了一个生成器对象
例如:

def create_num():
    a = 1
    while True:
        yield a
        a += 1
        if a == 10:
            break


obj = create_num()  # 创建生成器对象

resault = next(obj)
print(resault)
resault = next(obj)
print(resault)
print("-" * 10)
for i in obj:
    print(i)

打印结果:

这里创建的create_num就是一个生成器,当我们调用next方法时,它就会从yield中返回一个值,然后下一次调用next方法时,程序会继续从yield之后继续运行,而不是从头开始运行

send

我们除了可以用next()函数来唤醒生成器之外,我们也可以使用send()函数来唤醒,而且send()还可以在唤醒的时候在断点处传入一个值,前提是在yield前要有一个接受这个参数的值。obj.send(None)等价next(obj)


使用yield完成多任务

例如:

from time import sleep


def test1():
    while True:
        print(1)
        sleep(0.1)
        yield


def test2():
    while True:
        print(2)
        sleep(0.1)
        yield


def main():
    t1 = test1() 
    t2 = test2()
    while True:
        next(t1)
        next(t2)


if __name__ == '__main__':
    main()

前面我们说了,在函数中添加yield就会变成一个生成器,当调用next()方法时就会在yield出暂停,下次调用next会在暂停处继续执行,这么这两个函数就会交替执行,即并发执行

使用gevent完成多任务

为了更好的使用协程来实现多任务,python中的greenlet模块对其封装,大致原理就是使用switch()在两个函数间来回切换。

但是,greenlet还是得人工切换,所以python中还有一个比greenlet更强大且能够自动切换任务的模块gevent

  • 原理:当一个函数中出现阻塞或者耗时的时候,gevent就会自动利用阻塞或者耗时的这段时间去切换到下一个函数去执行,直到阻塞或耗时结束,再在合适的时候切换回来继续执行

  • 那怎么才能让gevent直到这是阻塞呢?
    比如延时sleep就得使用gevent.sleep(),socket中的阻塞也得使用gevent中的阻塞,这无疑是对代码的重构,这当然是不行的,所以要给程序打补丁

在程序的前面先从gevent中导出monkey,然后执行一下monkey.patch_all()就行了,他会把程序中的则色和延时自动替换成gevent的。

例如:

import time
# from time import time
import gevent
from gevent import monkey

monkey.patch_all()  # 将程序中用到的耗时操作的代码,换成gevent中自己实现的模块


def test1():
    while True:
        print(1)
        time.sleep(0.1)


def test2():
    while True:
        print(2)
        time.sleep(0.1)


def main():
    gevent.joinall([
        gevent.spawn(test1),
        gevent.spawn(test2),
    ])


if __name__ == '__main__':
    main()
  • 注意这里有一个,就是耗时操作不能直接将函数从模块中导出来,例如使用time中的sleep,之前我们都是习惯from time import time,然后后面直接使用sleep(x),但在这里不行,只能是import time之后在使用time.sleep(x),这样monkey.patch_all()才能将程序中用到的耗时操作的代码,换成gevent中自己实现的模块。
本文作者:Author:     文章标题:python多任务-协程
本文地址:https://yuaneuro.cn/archives/coroutine.html     
版权说明:若无注明,本文皆为“yuaneuro的小站”原创,转载请保留文章出处。
Last modification:July 22nd, 2020 at 03:03 am