可迭代的对象、迭代器和生成器

可迭代的对象、迭代器和生成器

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

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

内置的iter函数有一下作用。

  1. 检查对象是否实现了__iter__,如果实现了就调用它,获取一个迭代器。
  2. 如果没有实现__iter__方法,但是实现__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
  3. 如果尝试失败,Python抛出TypeError异常,通常会提示“C object is not iterable”

任何Python序列都可迭代的原因是,它们都实现了__getitem__方法。其实,标准的序列也都实现了__iter__

方法,之所以对__getitem__方法做特殊处理,是为了向后兼容。

例子:

import re
import reprlib

RE_WORD = re.compile(r'\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 __repr__(self):
        return 'Sentence(%s)' % reprlib.repr(self.text)

可迭代对象与迭代器的对比

可迭代的对象:

​ 使用iter内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。序列都可以迭代:实现__getitem__方法,而且其参数都是从零开始的索引,这种对象也可以迭代。

可迭代的对象与迭代器之间的关系:Python从可迭代的对象中获取迭代器

可迭代对象和迭代器要实现的接口

class Iterable(metaclass=ABCMeta):

    __slots__ = ()

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


class Iterator(Iterable):

    __slots__ = ()

    @abstractmethod
    def __next__(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

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

其UML图如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mZ2H7wu0-1586264083644)(D:\learn\liuchangdepython\images\Iterable和Iterator.png)]

IterableIterator抽象基类。以斜体显示的是抽象方法。具体的Iterable.__iter__方法应该返回一个Iterator实例。具体的Iterator类必须实现__next__方法。Iterator.__iter__方法直接返回实例本身。

迭代器

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

典型的迭代器

迭代器模式:

  1. 访问一个聚合对象的内容而无需暴露它的内部表示
  2. 支持对聚合对象的遍历
  3. 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)

例子:

import re
import reprlib

RE_WORD = re.compile(r'\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

Sentence.__iter__返回迭代器SentenceIterator,而SentenceIterator提供访问一个聚合对象的内容的实现。

可迭代的对象(如Sentence)一定不能是自身的迭代器。也就是说,可迭代的对象必须实现__iter__方法,但

不能实现__next__方法

生成器函数

更pythonic 的方法:使用生成器函数替代SentenceIterator,不在单独定义一个迭代器类

import re
import reprlib

RE_WORD = re.compile(r'\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

生成器的工作原理

​ python函数的工作原理

def foo():
    bar()
def bar():
    pass
foo()
  • python的解释器会用PyEval_EvalFramEx(C语言)函数去执行我们的foo函数.
  • 在运行foo函数的时候首先会创建一个栈帧(Stack frame), 这个栈帧是一个上下文, 也是一个对象.
  • 栈帧会将foo函数变成一个字节码对象, 使用dis查看字节码
  • 然后栈帧的上下文中去运行字节码(字节码是全局唯一的)
def foo():
    bar()
def bar():
    pass

import dis
print(dis.dis(foo))

2           0 LOAD_GLOBAL              0 (bar)
            2 CALL_FUNCTION            0
            4 POP_TOP
            6 LOAD_CONST               0 (None)
            8 RETURN_VALUE
None
  • 当foo调用bar, 又会创建一个新的栈帧, 然后运行bar的字节码
  • 所有的栈帧都是分配在堆的内存上, 如果不释放会一直存在, 所以栈帧可以独立于调用者存在, 就比如调用者foo不存在也没关系, 只要指针指向bar的栈帧就可以控制
import inspect
frame = None
def foo():
    bar()
def bar():
    pass
    global frame
    # 获取当前函数的栈帧并赋给全局变量frame
    frame = inspect.currentframe()

foo()
print(frame.f_code.co_name)
>>> bar
caller_frame = frame.f_back
print(caller_frame.f_code.co_name)
>>> foo

在这里插入图片描述

  • python解释器会编译字节码, 如果发现有yeild, 就会标记该函数, 然后再调用的时候会返回一个生成器对象. 而这个生成器对象实际上是把这个frame对象做了一个封装

在这里插入图片描述

  • 生成器可以在任何时候、任何函数中恢复运行,因为它的栈帧并不在真正的栈中,而是堆中
  • f_lasti指向“最后执行指令”的指针。初始化为 -1,意味着它没开始运行

惰性实现

设计 Iterator 接口时考虑到了惰性: next(my_iterator) 一次生成一个元素。 懒惰的反义词是急迫, 其实, 惰性求值(lazy evaluation) 和及早求值(eager evaluation) 是编程语言理论方面的技术术语。

re.finditer 函数是 re.findall 函数的惰性版本, 返回的不是列表, 而是一个生成器, 按需生成 re.MatchObject 实例。 如果有很多匹配, re.finditer函数能节省大量内存。

    def __iter__(self):
        for match in RE_WORD.finditer(self.text):
            yield match.group()

生成器表达式

生成器表达式可以理解为列表推导的惰性版本: 不会迫切地构建列表, 而是返回一个生成器, 按需惰性生成元素。 也就是说, 如果列表推导是制造列表的工厂, 那么生成器表达式就是制造生成器的工厂。

    def __iter__(self):
        for match in RE_WORD.finditer(self.text):
            yield match.group()

yield from

如果生成器函数需要产出另一个生成器生成的值,传统的解决方法是使用嵌套的for循环

def chain(*iterables):
    for it in iterables:
        for i in it:
            yield i


for i in chain([1, 3], (3, 4, 5)):
    print(i)

使用yield from

def chain(*iterables):
    for it in iterables:
        yield from it

使用yield from it完全替换了内层的for循环。 在这个示例中使用 yield from是对的, 而且代码读起来更顺畅, 不过感觉更像是语法糖。 除了代替循环之外, yield from 还会创建通道, 把内层生成器直接与外层生成器的客户端联系起来。 把生成器当成协程使用时, 这个通道特别重要, 不仅能为客户端代码生成值, 还能使用客户端代码提供的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值