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

推荐订阅源

S
SegmentFault 最新的问题
Security Latest
Security Latest
Martin Fowler
Martin Fowler
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
The Register - Security
The Register - Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
罗磊的独立博客
MyScale Blog
MyScale Blog
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
M
MIT News - Artificial intelligence
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
博客园 - 叶小钗
H
Help Net Security
大猫的无限游戏
大猫的无限游戏
U
Unit 42
G
Google Developers Blog
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
PCI Perspectives
PCI Perspectives
The Last Watchdog
The Last Watchdog
有赞技术团队
有赞技术团队
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News and Events Feed by Topic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Application and Cybersecurity Blog
Application and Cybersecurity Blog
雷峰网
雷峰网
SecWiki News
SecWiki News
Google Online Security Blog
Google Online Security Blog
S
Security @ Cisco Blogs
N
News | PayPal Newsroom
The Hacker News
The Hacker News
月光博客
月光博客
T
Threatpost
B
Blog
P
Proofpoint News Feed
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Simon Willison's Weblog
Simon Willison's Weblog
P
Palo Alto Networks Blog
L
LangChain Blog
Scott Helme
Scott Helme
Last Week in AI
Last Week in AI
C
Check Point Blog
P
Privacy International News Feed

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的文档真的很棒,希望你们都学得开心。