惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
M
MIT News - Artificial intelligence
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
IT之家
IT之家
F
Fortinet All Blogs
博客园 - 聂微东
U
Unit 42
Martin Fowler
Martin Fowler
腾讯CDC
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky

Xudong's Blog

AI杀死了个人博客 让MDC在各种线程间穿梭自如 新手选购微单的策略 一些让Python代码更快的技巧 HTTP报文结构 Python日志库logging总结 写定向爬虫时遇到的问题 Java中的协变与逆变 给Hexo主题添加LaTeX公式支持 Rss订阅源分享 Git Tips Redis的数据库与持久化 Redis中的数据结构 控制反转与依赖注入 Vim命令笔记 Java中的equals和hashCode方法 Java中对象域的初始化 Java中的基本类型和自动装拆箱 Hello, world. 使用Hexo搭建静态博客
Python3中的Iterator与Iterable
Xudong Sun · 2019-09-07 · via Xudong's Blog

iterable和iterator很容易弄混的吧!

定义

Iterator

An object representing a stream of data.

Python Glossary - Iterator

我们可以从Python官方的文档中了解到,Iterator - 迭代器 这个术语在python中的意义。
Iterator - 可以表示数据流的对象。
数据流 - 一组有顺序的(线性的)、有起点和终点的数据集合。

就像在管道中流淌的水流,数据流通常都是单向的。Python允许我们通过调用一个iterator对象上的__next__()方法,来访问数据流(iterator)中的下一个元素。另一种方法是使用内置函数next(),使用next(iterator_object)来获取iterator中的下一个元素。我们可以在同一个iterator上重复进行这样的操作,最终会按顺序获取数据流中的所有对象。

当iterator中的最后一个元素已经被访问之后,我们再使用next()方法,或者调用__next__(),解释器会抛出一个StopIteration异常,告诉我们此迭代器之前就已经迭代结束了,其中已经没有“下一个”元素了。

由此,我们可以简单地认为:如果一个对象拥有__next__()方法,我们就可以视它为iterator。

Iterable

An object capable of returning its members one at a time.

Python Glossary - Iterable

Iterable - 有能力一次性返回它的成员的对象。
这里需要解释这个“成员”是个带s的复数。
这个对象有一个方法来返回它容纳的一些元素,甚至全部元素。我们就可以称它为可迭代对象iterable。

怎么做到一次返回全部元素?这里不要产生理解偏差,不是指返回一个数组,其实是特指返回一个iterator。

我们学习过的所有线性数据类型,包括list, str, tuple,一些非线性数据类型,包括dictopen()函数返回的文件对象,都是可迭代对象。它们都有一个叫做__iter__()的方法,可以返回它们各自的迭代器。我们也可以使用内置函数iter(),传入一个可迭代对象,就可以返回一个它的迭代器对象。

另外,文档中明确说明了,任何定义了__iter__()__getitem__()的对象,都是iterable。但是要求__getitem__()方法实现的是一个线性的语义。这样是为了让其在for语法下与可迭代对象表现出同样的行为,为了让其生成的迭代器与其他迭代器同样是一个数据流。(如果__getitem__()实现的并不是线性的语义,解释器并不会报错,同样可以被for语法使用。但是语义错误同样是编程错误。)

可迭代对象一定是一个线性的数据类型。大部分线性的数据类型都是一个iterable。
我们可以简单地认为,只要实现了__iter__()__getitem__()方法的类,其对象都是iterable。

for…in

1
2
3
L = ['python', 'java', 'rust', 'c']
for item in L:
print(item)

所以我们可以明白for...in的语法到底为我们做了啥。
首先for item in L,会在L这个iterable上调用__iter__(),创建一个iterator对象,然后每轮循环都在在iterator上调用__next__()并把返回值赋给item,直到出现StopIteration异常,帮我们捕获后就会结束循环。

自己动手

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

Python推崇鸭子类型,你如果实现了__iter__()__getitem__()方法,(即把对象传入iter()可以得到一个Iterator)那你就是一个Iterable。同理,你如果实现了__next__()__iter__()就是一个Iterator。有时候,鸭子类型可以让我们很方便地免去继承来实现一些interface或者说是protocol。

自己的Iterator

Iterator Types

由文档中对于迭代器的类型的描述,我们得知,迭代器类必须实现一个__next__()方法用于返回下一个迭代元素,还有一个__iter__()方法返回自身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MyRange:
def __init__(self, low, high):
self.current = low
self.high = high

def __iter__(self):
return self

def __next__(self):
if self.current >= self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1

for i in MyRange(4,9):
print(i)

上面的代码实现了我们自己的range(),MyRange类实例化后产生的对象就是一个iterator。我们可以使用for循环对这个iterator进行迭代,等等!for…in的迭代对象不是iterable吗?为什么我们写的iterator也可以被for语法遍历呢?

写代码让我们发现了python规定迭代器类必须实现__iter__()的理由,很多时候我只是需要一个迭代器,但事实上很多地方使用的是可迭代对象协议。可迭代对象需要实现的是可以把迭代器返回出来,那么我们的迭代器自己实现__iter__()并且把自己返回出来,其实逻辑上是可以接受的。

对iterator进行迭代,是符合直觉的,因为迭代器本来就是可以迭代的。
对iterable进行迭代,也是符合直觉的,“可迭代的对象”本来也是可迭代的,每次迭代的时候在内部初始化一个iterator进行迭代。

iterator一定是一个iterable。

自己的Iterable

所以其实iterable就没有必要实现了,上面的MyRange本身就是一个iterable。

但你还记得上面定义吗,一个实现了线性语义的__getitem__()方法的对象也是一个iterable。另外还要求了__getitem__()方法的接收的参数是从0开始的整数。

object must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0).

1
2
3
4
5
6
7
8
9
10
11
class MyRange:
def __init__(self, low, high):
self.low = low
self.high = high
self._length = high - low

def __getitem__(self, index):
if index >= self._length or index < 0:
raise IndexError("index out of range")
else:
return index + self.low

这样实现的MyRange,是一个单纯的iterable。内置函数iter(),可以神奇地把它的对象转变成一个iterator。因为可以线性遍历元素的容器,在逻辑结构上是满足迭代器的结构的。

判断可迭代对象

for语法能不能作用在一个对象上?(这个对象是不是一个iterable或者iterator)
一个对象能不能作为next()函数的第一个参数?(这个对象是不是一个iterator)

在动态类型的python中,有时必要的类型判断可以避免很多问题。

在网上搜索,很多文章给出的判断方法是:

1
2
3
>>> from collections import Iterable
>>> isinstance(['abc', 'def'], Iterable)
>>> True

然后我得到的结果是:

1
2
3
4
>>> from collections import Iterable
__main__:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
>>> isinstance(['abc', 'def'], Iterable)
True

我目前使用的是Python 3.7.4,Warning中也明确说了,3.8以后我们就不能再从collections中import或者使用ABCs(Abstract Base Classes)。所以我们得改成:

1
2
3
4
5
6
7
>>> from collections.abc import Iterable, Iterator
>>> isinstance(['abc', 'def'], Iterable)
True
>>> isinstance(['abc', 'def'], Iterator)
False
>>> isinstance(iter(['abc', 'def']), Iterator)
True

Generator

生成器本身一定是Iterator,并且它也可以用于实现iterable的__iter__()方法。有两种方法可以方便地创建生成器,其中第二种方法涉及到yield语句,我不想再展开说,以后如果有时间再另写一篇关于yield语句以及和它有关的协程。

生成器表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> g = (num for num in range(4,9))
>>> type(g)
<class 'generator'>
>>> isinstance(g, collections.abc.Iterator)
True
>>> for i in g:
... print(i)
...
4
5
6
7
8

生成器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def my_range(low, high):
while True:
if low >= high:
break
yield low
low += 1

g = my_range(4, 9)

print(type(g))
print(isinstance(g, collections.abc.Iterator))

for i in g:
print(i)

尾巴

最后用一张来自Vincent Driessen的一篇博文Iterables vs. Iterators vs. Generators的图片来总结下:

很长时间没用就是会让记忆变得模糊哈,多查文档多用搜索引擎,python的文档真的很棒,希望你们都学得开心。