迭代器、生成器、协程

1.可迭代对象

  • 使用iter内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__函数,那么对象就是可迭代的。

  • 实现了__getitem__方法, 而且其参数是从0开始的索引,这种对象也是可以迭代的

for item in x: => 等价于it = iter(x),一直执行next(it),每个for 循环返回的iter不是同一个对象

1.1 Sentence 类第一版:单词序列

import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence(object):
    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)


if __name__ == '__main__':
    s = Sentence('"welcome to china" liu bei said')
    print(f"s:{s}")

    for word in s:
        print(word)

    print(list(s))

    print(s[0], s[1], s[2])

    print(issubclass(Sentence, abc.Iterable))
# out:
"""
s:Sentence('"welcome to ... liu bei said')
welcome
to
china
liu
bei
said
['welcome', 'to', 'china', 'liu', 'bei', 'said']
welcome to china
Flase
"""   

1.2 序列可迭代的原因:iter函数

解释器迭代对象x时,会自动调用iter(x)

内置的iter函数有以下作用:
(1)检查对象是否实现了__iter__方法,如果实现就调用它,返回一个迭代器
(2)若没有,但是实现了__getitem__,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素
(3)如果尝试失败,Python会抛出TypeError异常:C object is not iterable

所有Python对象都可迭代的原因是,按照定义,序列都实现了__getitem__方法。其实,标准的序列也都实现了__iter__方法。因此你也应该这么做。 之所以能通过__getitem__方法迭代,是为了向后兼容,而未来可能不会这么做。可是Python3.10 还为废弃这个行为也有可能根本就不会移除

如果一个类提供了__getietm__方法,那么iter() 接受的可迭代对象就是该类的实例,据此构建一个迭代器。Python 的迭代机制以从0开始的索引调用
__getitem__方法,没有剩余项时抛出IndexError。

虽然Sentence 的实例可以迭代,但是无法通过针对abc.Iterable 的isinstance() 检查

在大鹅理论中,可迭代对象的定义简单些,不过没那么灵活:如果实现了__iter__方法。那就认为对象是可迭代的。此时,不需要子类化,也不用注册,因为
abc.Iterable 实现了__subclasshook__

例如:

>>> class GooseSpam:
...     def __iter__(self):
...             pass
...
>>> from collections import abc
>>> issubclass(GooseSpam,abc.Iterable)
True
>>> goose_spam = GooseSpam()
>>> isinstance(goose_spam,abc.Iterable)
True
>>>

Iterable 的源码入如下:

class Iterable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

1.3 可迭代对象与迭代器

可迭代对象与迭代器的关系:我们从可迭代对象中获取迭代器对象

Python 标准的迭代器接口有以下两个方法:

  • __next__
    返回序列的下一项,如果没有项了,则抛出StopIteration

  • __iter__
    返回self,以便在预期可迭代对象的地方使用迭代器,例如self

以斜体显示的是抽象方法

2.迭代器

实现了无参数的__next__方法,返回序列中下一个元素,如果没有元素了,那么抛出StopIteration异常。
Python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代

2.1 Sentence第二版:典型的迭代器

import re
import reprlib

RE_WORD = re.compile('\w+')


class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(self.text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        return SentenceIterator(self.words)


class SentenceIterator(object):
    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


if __name__ == '__main__':
    s  = Sentence('how old are you')

    for el in s:
        print(el)

    print(s[1])  # TypeError: 'Sentence' object is not  

#out:
"""
how
old
are
you
Traceback (most recent call last):
  File "E:\PyProject\study\python实现web_ui\test_11.py", line 43, in <module>
    print(s[1])
TypeError: 'Sentence' object is not subscriptable
"""

构建可迭代对象和迭代器时经常会出现错误,原因是二者混淆了。要知道,可迭代对象有个__iter__方法,每次都实例化一个新的迭代器;
而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身。因此,迭代器可以迭代,但是可迭代的对
像不是迭代器

3. Sentece 第三版:生成器函数



import re
import reprlib

RE_WORD = re.compile('\w+')

class Sentence(object):
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(self.text)

    def __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

    def __iter__(self):
        for el in self.words:
            yield el
        return

if __name__ == '__main__':
    s = Sentence('You deceive you change')

    print(list(s))
    
    for el in s:
        print(el)

    print(s)

#out:
"""
['You', 'deceive', 'you', 'change']
You
deceive
you
change
Sentence('You deceive you change')
"""

3.1 生成器函数的工作原理

>>> def gen_123():
...     yield 1
...     yield 2
...     yield 3
...
>>> gen_123
<function gen_123 at 0x000001D46E433E20>
>>> gen_123()
<generator object gen_123 at 0x000001D46E8782E0>
>>> for i in gen_123():
...     print(i)
...
1
2
3
>>> g = gen_123()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

生成器函数会创建一个生成器对象,包装生成器函数的定义体。把生成器传给next()函数时,生成器函数会向前,执行函数体定义
中的下一个yield语句,返回产出的值,并在函数定义体的当前位置暂停。最终,函数的定义体返回时,外层的生成器对象会抛出
StopIteration异常-----这一点与迭代器协议一致

yield from: 从子生成器中产出

Python 3.3 新增的yield from表达式句法可以把一个生成器的工作委托给一个子生成器。 引入yield from 之前,如果一个生成器根据另一个生成器生成的值产出值,则需使用for 循环

>>> def sub_gen():
...     yield 1.1
...     yield 1.2
...
>>> def gen():
...     yield 1
...     for i in sub_gen():
...             yield i
...     yield 2
...
>>> for x in gen():
...     print(x)
...
1
1.1
1.2
2

# 使用yield from 可以达到相同的效果
>>> def gen():
...     yield 1
...     yield from sub_gen()
...     yield 2
...
>>> for x in gen():
...     print(x)
...
1
1.1
1.2
2

for 循环是客户代码,gen是委托生成器,sub_gen 是子生成器。注意,yield from 暂停gen,sub_gen接手, 直到它耗尽。sub_gen产出的值绕过gen,直接传给客户代码中的for 循环。在此期间,gen处于暂停状态,看不到绕过它的那些值,当sub_gen耗尽后,gen回复执行

子生成器中有return 语句时,返回一个值,在委托生成器中,通过含有yield from 的表达式可以捕获那个值,如下列:

>>> def sub_gen():
...     yield 1.1
...     yield 1.2
...     return 'Done!'
...
>>> def gen():
...     yield 1
...     result = yield from sub_gen()
...     print('<--',result)
...     yield 2
...
>>> for x in gen():
...     print(x)
...
1
1.1
1.2
<-- Done!
2

了解了 yield from 的基本作用后,下面通过几个简单的示例说明它的实际用途

  1. 重新实现chain
>>> from itertools import chain
>>> list(chain(range(3),'ABC'))
[0, 1, 2, 'A', 'B', 'C']

可以使用嵌套的for 循环来自己实现chain函数:

>>> def my_chain(*iterables):
...     for iter in iterables:
...             for i in iter:
...                     yield i
...
>>> s = 'ABC'
>>> r = range(3)
>>> list(my_chain(r,s))
[0, 1, 2, 'A', 'B', 'C']

内部循环可以替换成yield from 表达式:

>>> del my_chain
>>> def my_chain(*iterables):
...     for iter in iterables:
...             yield from iter
...
>>> list(my_chain(r,s))
[0, 1, 2, 'A', 'B', 'C']
  1. 遍历树状结构

截至Python 3.10 异常层次结构分为5层,最顶层是BaseException,我们的第一步就是显示第最顶层

def tree(cls):
    yield cls.__name__

def display(cls):
    for cls_name in tree(cls):
        print(cls_name)

if __name__ == '__main__':
    display(BaseException)

输出第二层:

def tree(cls):
    yield  cls.__name__,0
    for sub_cls in cls.__subclasses__():
        yield sub_cls.__name__,1

def display(cls):
    for cls_name,level in tree(cls):
        indent = ' '* 4 * level
        print(f'{indent}{cls_name}')

if __name__ == '__main__':
    display(BaseException)

'''
BaseException
    Exception
    GeneratorExit
    SystemExit
    KeyboardInterrupt
'''

抽取sub_tree 方法:

def tree(cls):
    yield cls.__name__,0
    yield from sub_tree(cls)

def sub_tree(cls):
    for sub_cls in cls.__subclasses__():
        yield sub_cls.__name__, 1

def display(cls):
    for cls_name,level in tree(cls):
        indent = ' '* 4 * level
        print(f'{indent}{cls_name}')

if __name__ == '__main__':
    display(BaseException)

输出第三层:

def tree(cls):
    yield cls.__name__,0
    yield from sub_tree(cls)

def sub_tree(cls):
    for sub_cls in cls.__subclasses__():
        yield sub_cls.__name__, 1
        for sub_sub_cls in sub_cls.__subclasses__():
            yield sub_sub_cls.__name__,2

def display(cls):
    for cls_name,level in tree(cls):
        indent = ' '* 4 * level
        print(f'{indent}{cls_name}')

if __name__ == '__main__':
    display(BaseException)

'''
BaseException
    Exception
        TypeError
        StopAsyncIteration
        StopIteration
        ImportError
        OSError
        EOFError
        RuntimeError
        NameError
        AttributeError
        SyntaxError
        LookupError
        ValueError
        AssertionError
        ArithmeticError
        SystemError
        ReferenceError
        MemoryError
        BufferError
        Warning
        _OptionError
        error
        Verbose
        Error
        TokenError
        StopTokenizing
    GeneratorExit
    SystemExit
    KeyboardInterrupt
'''

将sub_tree 重构为递归调用:

def tree(cls):
    yield cls.__name__, 0
    yield from sub_tree(cls, 1)


def sub_tree(cls, level):
    for sub_cls in cls.__subclasses__():
        yield sub_cls.__name__, level
        yield from sub_tree(sub_cls, level + 1)

def display(cls):
    for cls_name, level in tree(cls):
        indent = ' ' * 4 * level
        print(f'{indent}{cls_name}')


if __name__ == '__main__':
    display(BaseException)

'''
BaseException
    Exception
        TypeError
        StopAsyncIteration
        StopIteration
        ImportError
            ModuleNotFoundError
            ZipImportError
        OSError
            ConnectionError
                BrokenPipeError
                ConnectionAbortedError
                ConnectionRefusedError
                ConnectionResetError
            BlockingIOError
            ChildProcessError
            FileExistsError
            FileNotFoundError
            IsADirectoryError
            NotADirectoryError
            InterruptedError
            PermissionError
            ProcessLookupError
            TimeoutError
            UnsupportedOperation
        EOFError
        RuntimeError
            RecursionError
            NotImplementedError
            _DeadlockError
        NameError
            UnboundLocalError
        AttributeError
        SyntaxError
            IndentationError
                TabError
        LookupError
            IndexError
            KeyError
            CodecRegistryError
        ValueError
            UnicodeError
                UnicodeEncodeError
                UnicodeDecodeError
                UnicodeTranslateError
            UnsupportedOperation
        AssertionError
        ArithmeticError
            FloatingPointError
            OverflowError
            ZeroDivisionError
        SystemError
            CodecRegistryError
        ReferenceError
        MemoryError
        BufferError
        Warning
            UserWarning
            EncodingWarning
            DeprecationWarning
            PendingDeprecationWarning
            SyntaxWarning
            RuntimeWarning
            FutureWarning
            ImportWarning
            UnicodeWarning
            BytesWarning
            ResourceWarning
        _OptionError
        error
        Verbose
        Error
        TokenError
        StopTokenizing
    GeneratorExit
    SystemExit
    KeyboardInterrupt

'''

任何靠谱的递归函数都会强调,必须提供一个基准情形,以防止无限递归。基准情形是一种条件分支。通常使用if语句 实现,一旦满足就返回,不再递归调用。在上例中,sub_tree没有这样一个if语句,但是for循环隐含一个条件,如果 cls.__subclasses__()返回一个空列表,则循环主体不执行,因此也就不递归调用

进一步重构:

def tree(cls,level=0):
    yield cls.__name__, level
    # yield from tree(cls, level + 1)
    for sub_cls in cls.__subclasses__():
        yield from tree(sub_cls, level + 1)

def display(cls):
    for cls_name, level in tree(cls):
        indent = ' ' * 4 * level
        print(f'{indent}{cls_name}')

if __name__ == '__main__':
    display(BaseException)

泛化可迭代类型

iterator 类型没有iterable 类型那么常用,但是编写方式也不难。下例是我们熟悉的斐波那契数列生成器,附带注解

from collections.abc import  Iterator

def fibonacci() -> Iterator[int]:
    a,b = 0,1
    while True:
        yield a
        a,b = b, a + b

注意,Iterator 类型用于注解含有yield 关键字的生成器函数,以及我们自己动手编写的带有__next__方法的迭代器类。此外, 还有一个collection.abc.Generator 类型,用于注解生成器对象,但是把生成器当成迭代器使用时,无须多此一举

根据下例你会发现,Iterator类型其实是Generator类型简化的特殊情况

from collections.abc import  Iterator
from keyword import kwlist
from typing import TYPE_CHECKING

short_kw = (k for k in kwlist if len(k) < 5)

if TYPE_CHECKING:
    reveal_type(short_kw)

long_kw: Iterator[str] = (k for k in kwlist if len(k) >=4 )

if TYPE_CHECKING:
    reveal_type(long_kw)

运行:mypy demo666.py 后输出结果:

demo666.py:8: note: Revealed type is "typing.Generator[builtins.str, None, None]"
demo666.py:13: note: Revealed type is "typing.Iterator[builtins.str]"
Success: no issues found in 1 source file

abc.Iterator[str] 与abc.Generator[str,None,None] 相容。因此Mypy 检查没有报错 Iterator[T]是Generator[T,None,None]的简写形式,二者的意思都是"产出项的类型为T,但是不利用或返回值的生成器"。能利用和返回值的生成器是协程

经典协程


'''
经典协程其实就是生成器,只不过以另一种方式使用。
tupe 实例可以用作记录,也可以用作不可变序列。有做记录时,元组的项数时固定的,每一项可以是不同类型的值
用作不可变列表时,元组的长度随意,所有项都具有相同的类型。因此,使用类型注解元组有两种方式
'''
# 一个城市记录,包含名称,国家,人口:
city: tuple[str,str,int]
# 一个不可变序列,包含一系列域名:
domains: tuple[str,...]

'''
生成器也有类型情况。生成器通常用作迭代器,但是也可以用做协程。协程其实就是生成器函数,通过主体中含有yield 关键字的函数创建
二者的使用情形差别很大,因此类型提示的写法也不相同
'''

# readings可以绑定产出float 值的迭代器或生成器
readings: Iterator[float]

# sim_taxi变量可以绑定一个表示出租车的协程,模拟离散事件
# 该变量产出事件,接受浮点数时间戳,返回仿真过程中的行程次数
sim_taxi: Generator[Event,float,int]

typing 模块的文档像下面这样描述Generator 的形式类型参数 Generator[YieldType,SendType,ReturnType] 仅当把生成器用作协程时,SendType 才有意义。那个类型参数是gen.send(x) 调用中x的类型。倘若创建生成器的目的是用作迭代器,则调用.send()将报错 同样ReturnType 也只在注解协程时才有意义,因为迭代器不像常规函数那样可以返回值。对于用于迭代器的生成器唯一合理的操作是调用netx(it),也可以 通过for循环和其他迭代形式简介调用。YieldType 是next(it)调用返回值的类型

Generator 类型的类型参数与typing.Coroutine相同
Coroutine[YieldType,SendType,ReturnType]
typing.Coroutine (已弃用)和 collections.abc.Coroutine (自Python3.9 起可用的泛型)仅用于注解原生协程,不能注解经典协程。经典协程的类型提示
是能使用含糊不清的Generator[YieldType,SendType,ReturnType]

示例:使用协程计算平均值

from collections.abc import Generator

def averager() -> Generator[float,float,None]:
    count=0
    average=0
    total=0
    while True:
        term = yield average
        total += term
        count +=1
        average = total / count

if __name__ == '__main__':
    a = avager()
    print(next(a))
    print(a.send(10))
    print(a.send(30))
    print(a.send(5))

'''
0
10.0
20.0
15.0
'''

使用协程的好处是,total和count声明为局部变量即可,在协程暂停并等待下一次调用.send()期间,无须使用实例属性或闭包保持上下文。正是这一点 吸引人们在异步编程中把回调换成协程,因为在多次激活之间,协程能保持局部状态。

调用next(a) 后,协程向前执行到yield, 产出变量average 变量的初始值。也可以调用a.send(None)【等价于next(a)】 开始执行协程--这其实就是内置函数next()的作用。
但是不能发送None之外的值,因为协程只能在yield 处暂停时接受发送的值。调用next() 或 .send(None) 向前执行到第一个yield 的过程 叫做"预激协程"

每次激活之后,协程在yield 处暂停,等待发送值。a.send(10) 那一行发送一个值,激活协程,yield 表达式把得到的值10赋给term变量。循环余下的部分更新total,count,averge这3个变量的值。while 循环的下一次迭代产出average 变量的值,协程在yield 关键字处再一次暂停

posted @ 2022-02-15 20:50  chuangzhou  阅读(31)  评论(0编辑  收藏  举报