html生成器_Python 可迭代对象, 迭代器与生成器

24853f2a4f6f906c408da2efeabff628.png

这篇文章主要讨论下 Python 中的容器迭代协议,它的实现统一约定了容器对象的迭代方案,允许我们自定义类对迭代的支持。在 Python 中有很多的协议,比如 迭代器协议描述器协议等等,这些 协议比较类似于其他面向对象语言中 接口的概念。

本文主要介绍内容为可迭代对象、迭代器和生成器。

可迭代对象(iterable)

以通俗简单的方式来讲,可迭代对象就是能够逐一返回其成员项的对象,其可用于for循环以及许多其他需要一个序列的地方zipmap等等。例如:

  • 序列类型:如 list, str, tuple, bytes

  • 非序列类型类型如:dict, 文件对象等

严格来说,可迭代对象是任何实现了__iter__()方法或者实现了序列(Sequence)语义中的__getitem__()方法的任意自定义类对象

那么下面我们分别来讨论下这两种实现方案。

1. 实现 __iter__

这种方式是你必须为你的类实现__iter__()方法,该方法返回的必须是一个迭代器对象,下面会讲到。

2. 实现 __getitem__

序列(Sequence)的迭代通过实现__getitem__()方法来使用整数索引进行高效的元素访问。同时,你必须为其定义一个返回序列长度的 __len__() 方法。例如上面例子中的 list, str, tuple, bytes。序列数据结构的存储是一段连续的内存空间,其直接使用整数索引进行寻址,查找元素非常高效,但是插入删除元素时效率低下。

有的童鞋会说,dict 也实现了 __getitem____len__ 协议,为毛dict不属于序列?原因是它并不是通过整数索引来顺序迭代,而是通过任意的不可变键(immutable key)来进行逐个查找的。所以dict的迭代还是因为其实现了__iter__

工作方式

当把一个可迭代对象x作为参数传给内置函数iter()时,它会返回该对象的迭代器以供迭代。但通常我们并不需要这么搞,当我们使用for对可迭代对象进行遍历时,for语句会为你自动处理那些操作,即自动调用iter(x)来获取迭代器,若对象没有实现__iter__()方法,而实现了方式 2 的__getitem____len__协议,其会自动创建一个迭代器,并尝试从索引 0 开始获取元素,若尝试失败,会引发一个 TypeError

类型断言

判断一个对象是否是一个可迭代对象,可以使用collections.abc.Iterable

In [1]: from collections.abc import Iterable

In [2]: isinstance([1, 2, 3], Iterable)
Out[2]: True

In [3]: isinstance(1, Iterable)
Out[3]: False

In [4]: isinstance('1', Iterable)
Out[4]: True

In [5]: isinstance({}, Iterable)
Out[5]: True

迭代器(iterator)

上面多次提到了迭代器,那么什么是迭代器?

实现了迭代器协议的对象就是一个迭代器,迭代器协议由__iter__()+__next__()共同组成。以 Golang 接口的思维理解就是任何实现了这俩方法的对象就是迭代器。

是的,又有__iter__(),所以迭代器必定也是可迭代对象。在迭代器中:

  • __iter__()必须返回迭代器对象本身。

  • __next__()应从容器中返回下一项,如果已经没有数据项可返回时,则需引发StopIteration异常,继续调用其__next__() 应再次引发StopIteration异常。

所以一个迭代器应有的样子应该是这样的:

class Iterator:

    def __iter__(self):
        return self

    def __next__(self):
        pass

工作方式

迭代器用来表示一连串数据流的对象,当for语句自动返回可迭代对象的迭代器时,for将重复调用其__next__()方法将逐个返回流中的项,直到迭代器引发StopIteration异常后终止循环。此时该迭代器数据项已耗尽,不能再使用。我们可以通过将迭代器传给next()函数来模拟:

In [11]: iterator = iter([1, 2, 3]) # 手动创建一个迭代器

In [12]: for i in iterator:
   ...:     print(i)
   ...:
1
2
3

In [13]: next(iterator) # 该迭代器已耗尽
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
-13-4ce711c44abc> in 
----> 1 next(iterator)
StopIteration:
In [14]: iterator = iter([1, 2, 3])
In [15]: next(iterator)
Out[15]: 1
In [16]: next(iterator)
Out[16]: 2
In [17]: next(iterator)
Out[17]: 3
In [18]: next(iterator)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)-18-4ce711c44abc> in 
----> 1 next(iterator)
StopIteration:

类型

判断一个对象是否是一个迭代器,可以使用collections.abc.Iterator

In [31]: from collections.abc import Iterator

In [32]: iterator = iter([1, 2, 3])

In [33]: isinstance(iterator, Iterator)
Out[33]: True

In [34]: isinstance([],Iterator)
Out[34]: False

练习

通过上面的介绍,你可能或多或少的了解了什么是可迭代对象 iterable,什么是迭代器 iterator。一句话简单的总结就是,当我们对可迭代对象进行迭代时,就从可迭代对象获取迭代器进行迭代。

下面我们来写一个非常简单的一段文本处理的示例,从下面一段 HTML 文本中提取出所有a标签的href

TEXT = ('
More '''/python-software-foundation-fellow.html">Python Software Foundation Fellow Members for''Q3 2019 '/~3/lE0u-5MIUQc/why-sponsor-pycon-2020.html">Why Sponsor PyCon 2020?'"http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/jAMRqiPhWSs/seeking-developers''-for-paid-contract.html">Developers for Paid Contract Improving pip
')
class LinkFinder:

    PATTERN = "(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"

    def __init__(self, text):
        self.links = re.findall(self.PATTERN, text)

    def __iter__(self):
        return LinkIiterator(self.links)

class LinkIiterator:

    def __init__(self, links):
        self.links = links
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        try:
            link = self.links[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return link

稍稍验证一下:

In [39]: for link in LinkFinder(TEXT):
    ...:     print(link)
    ...:
https://blog.python.org
http://feedproxy.google.com/~r/PythonSoftwareFoundationNew/~3/T3r7qZxo-xg/python-software-foundation-fellow.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/lE0u-5MIUQc/why-sponsor-pycon-2020.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/jAMRqiPhWSs/seeking-developers-for-paid-contract.html

生成器(generator)

看完上面的例子,有同学可能会想,能不能省掉LinkIiterator呢?有没有一种机制能够让__iter__()返回的对象自动提供__iter__()__next__()方法呢?

有,那就是生成器(generator)。

一个包含yield的函数就是一个生成器函数(通常称其为生成器),当其被调用的时,会返回一个迭代器,称为生成器迭代器(generator iterator)。yield表达式会产生一系列值来供给for循环使用或是通过next()函数逐一获取。每个yield会临时暂停处理,记住当前位置执行状态(包括局部变量,指令指针,内部求值栈和任何异常处理的状态等),当该生成器迭代器恢复时,它会从离开位置继续执行,这与每次调用都从新开始的普通函数差别很大。因本文主要讲迭代协议,所以关于生成器的实现部分暂不具体讨论。

生成器机制提供了一种实现迭代器协议的便捷方式。即如果我们把容器对象__iter__()方法实现为一个生成器,那么它将返回一个生成器迭代器对象,该对象提供__iter__()__next__()方法以供迭代。

我们将上面的例子改成生成器版:

class LinkFinder:

    PATTERN = "(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"

    def __init__(self, text):
        self.links = re.findall(self.PATTERN, text)

    def __iter__(self):
        for link in self.links:
            yield link

验证下:

In [40]: for link in LinkFinder(TEXT):
    ...:     print(link)
    ...:
https://blog.python.org
http://feedproxy.google.com/~r/PythonSoftwareFoundationNew/~3/T3r7qZxo-xg/python-software-foundation-fellow.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/lE0u-5MIUQc/why-sponsor-pycon-2020.html
http://feedproxy.google.com/~r/PythonSoftwareFoundationNews/~3/jAMRqiPhWSs/seeking-developers-for-paid-contract.html

优化

本小节其实属于多余部分,关于迭代协议的讨论基本已经结束,但通过上面关于生成器的介绍我们发现,生成器迭代器与普通迭代器的一个显著区别就是,其往往是随用随生成,而不是一次性生成完毕,这在迭代大量数据时将非常有用,不用一次性计算或载入全部的数据到内存。所以上面的例子还有待优化。因为links列表是一次性计算好的。可以使用re.finditer方法,对links进行惰性求值:

class LinkFinder:

    PATTERN = "(?<=href=\").+?(?=\")|(?<=href=\').+?(?=\')"

    def __init__(self, text):
        self.links = re.finditer(self.PATTERN, text)

    def __iter__(self):
        for link in self.links:
            yield link.group()

还可以使用一种更加简洁的方式,即使用生成器表达式

def __iter__(self):
    return (link.group() for link in self.links)
--end--有任何疑问欢迎点击下方?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值