2088 字
10 分钟
第14章 可迭代的对象、迭代器和生成器
2020-03-30

第14章 可迭代的对象、迭代器和生成器#

[toc]

可迭代对象#

举个例子:

import re
re_word = re.compile(r'\w+')
class Sentence(object):
def __init__(self, text):
self.text = text
self.word = re_word.findall(text)
def __getitem__(self, item):
return self.word[item]
def __len__(self):
return len(self.word)
def __str__(self):
return 'Sentence(%s)' % self.word
if __name__ == "__main__":
s = Sentence("Today is Tuesday")
print(s)
for word in s:
print(word)

返回结果:

Sentence(['Today', 'is', 'Tuesday'])
Today
is
Tuesday

我们知道一个对象可以迭代是因为实现了__iter__方法, 但是在Sentence中并没有实现__iter__方法。那为什么可以迭代呢。

原因在于在python中实现了iter和getitem的都是可迭代的。首先会检查是否实现了iter方法,如果实现了则调用,如果没有但是实现了__getitem__方法。

Python就会创建一个迭代器。

尝试按照顺序获取元素。如果尝试失败则会抛出typeerror异常,提示object is not iterable.

因此:

如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。 如果实现了__getitem__方法,而且其参数是从零开始的索引。

这种对象也可以迭代。

我们用__iter__方法来改造之前的Sentence。 在__iter__中返回一个可迭代对象iter(self.word)。 当执行for word in s的时候就会调用__iter__方法

import re
re_word = re.compile(r'\w+')
class Sentence(object):
def __init__(self, text):
self.text = text
self.word = re_word.findall(text)
def __iter__(self):
return iter(self.word)
def __len__(self):
return len(self.word)
def __str__(self):
return 'Sentence(%s)' % self.word
if __name__ == "__main__":
s = Sentence("Today is Tuesday")
print(s)
for word in s:
print(word)

再来看下next, next的作用是返回下一个元素,如果没有元素了,抛出stopIteration异常。

我们来看下next的使用方法。如果要遍历一个字符串,最简单的方法如下:

s = 'abc'
for char in s:
print(char)

如果不用for方法,代码需要修改如下:

s = 'abc'
it = iter(s)
while True:
try:
print(next(it))
except StopIteration:
del it
break

首先将s变成一个iter对象,然后不断调用next获取下一个字符,如果没有字符了,则会抛出StopIteration异常释放对it的引用

s = 'abc'
it = iter(s)
print(next(it))
print(next(it))
print(next(it))
print(next(it)) # StopIteration

因为只有3个字符,但是调用了4次it.next()导致已经找不到字符因此抛出异常。

总结:

可迭代对象:实现了__iter__方法,就是可迭代的,可以返回自身作为迭代器。 也可以返回其他一个可迭代对象

迭代器#

迭代器是什么#

迭代器:在Python2中实现了next方法,在python3中实现了__next__方法。

首先要让x通过iter变成一个可迭代对象,然后使用迭代器来调用元素

使用迭代器好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。

Alt text

迭代器内存消耗检测#

可以看看它的内存占用:

使用python2.7.15+:

import sys
i = iter(range(1000000))
print sys.getsizeof(i)
r = range(1000000)
print sys.getsizeof(r)
y = xrange(1000000) # 注意 xrange跟range的区别: xrange是range的迭代器的表达方式
print sys.getsizeof(y)

结果返回:

56
8000064
32

使用python 3.6+

import sys
i = iter(range(1000000))
print (sys.getsizeof(i))
r = range(1000000)
print (sys.getsizeof(r))

返回结果:

32
48

这里有个问题:为什么在python2跟Python3的运行结果相差这么大呢? 这是因为python3内部机制已经将range转换成了一个迭代器了。

这里可以看的出来适合大的数据,比如好几个G的数据, 使用了迭代器 内存使用大幅度减少,这是迭代器最大的优点。

总结下: 我们简单说迭代器就是访问集合元素,迭代器就是有一个next()方法的对象,而不是通过索引来计数的。

迭代器使用#

那么我们怎么能访问迭代器里面的元素呢?

迭代器有两个方法 ,分别是**iter()和next()**方法

这两个方法是迭代器最基本的方法 一个用来获得迭代器对象,一个用来获取容器中的下一个元素。

itertools是python提供的非常高效创建与使用迭代器的模块

from itertools import chain
test = chain.from_iterable('ABCDEFG')
# print(test)
# print(dir(test)) # 查看类具体方法
print(test.__next__()) # 'A'
print(test.__next__()) # 'B'
print(test.__next__()) # 'C'
test2 = chain('AB', 'CDE', 'F')
print(list(test2)) # ['A', 'B', 'C', 'D', 'E', 'F']

我们知道迭代器是不支持索引的,原因就是索引需实现明元素确占用的内存地址,而迭代器是用到元素的时候才会创建。如下:

i = iter(range(3)) # 创建迭代器
# i.index(2) # 获取元素为2的索引
# AttributeError: 'range_iterator' object has no attribute 'index'
# 列表
l = range(3)
print(l.index(2)) # 获取索引2

这个时候可以使用内建函数enumerate(),这个函数很重要。

for i, j in enumerate(iter(['A', 'B', 'C'])):
# for i, j in enumerate(['A', 'B', 'C']):
print(i, j)

运行结果返回:

0 A
1 B
2 C

可以看下这个函数的源码是怎么写的 ```python class enumerate(Iterator[Tuple[int, _T]], Generic[_T]): def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ... def __iter__(self) -> Iterator[Tuple[int, _T]]: ... if sys.version_info >= (3,): def __next__(self) -> Tuple[int, _T]: ... else: def next(self) -> Tuple[int, _T]: .. ```

生成器#

生成器是迭代器,但你只能遍历它一次(iterate over them once)

因为生成器并没有将所有值放入内存中,而是实时地生成这些值

mygenerator = (x * x for x in range(3)) # 生成器
# mygenerator = [x * x for x in range(3)] # 列表
for i in mygenerator:
print("i=", i)
for i in mygenerator:
print("New=", i)

运行结果:

i= 0
i= 1
i= 4

注意,你不能执行for i in mygenerator第二次,因为每个生成器只能被使用一次

yield生成器#

在Python中,使用生成器可以很方便的支持迭代器协议。

生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回, 而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。

也就是说,yield是一个语法糖,内部实现支持了迭代器协议

同时yield内部是一个状态机,维护着挂起和继续的状态。

下面看看生成器的使用:

def Zrange(n):
i = 0
while i < n:
yield i
i += 1
zrange = Zrange(3)
print(zrange) # <generator object Zrange at 0x000002997DE75468>
print([i for i in zrange]) # [0, 1, 2]

在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。

其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。 要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。

如同迭代器一样,我们可以使用next()函数来获取下一个值。

生成器执行过程#

例子:

def Zrange(n):
print("begin of Zrange")
i = 0
while i < n:
print("before yield:", i)
yield i
i += 1
print("after yield:", i)
print("begin of Zrange")
zrange = Zrange(3)
# print(zrange)
print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
print("-" * 10)
print(zrange.__next__())
# print(zrange.__next__()) # StopIteration

运行结果:

begin of Zrange
before yield: 0
0
----------
after yield: 1
before yield: 1
1
----------
after yield: 2
before yield: 2
2

通过结果可以看到:

  • 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。
  • 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止
  • next()方法的返回值就是yield语句处的参数(yielded value)
  • 当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常

总结:生成器是迭代器的一种,但功能方法比迭代器多

生成器创建#

生成器的创建可以使用yield关键字, 也可以使用生成器表达式

(x*2 for i in range(10))

生成器判断#

判断是否为生成器函数可用isgeneratorfunction, 判断是否为生成器对象可用isgenerator

from inspect import isgeneratorfunction, isgenerator
g = (i for i in range(3))
print(isgenerator(g)) # True
def demo():
yield 1
print(isgenerator(demo())) # True
print(isgeneratorfunction(demo())) # False
print(isgeneratorfunction(demo)) # True

yield与协程#

def coroutine():
print("coroutine start...")
result = None
while True:
s = yield result
result = 'result:{}'.format(s)
c = coroutine()
c.send(None) # coroutine start...
print(c.send("first")) # result:first
print(c.send("second")) # result:second
c.close()
# c.send("hello") # StopIteration
第14章 可迭代的对象、迭代器和生成器
https://blog.zhequtao.com/posts/python/流畅的python/第14章可迭代的对象迭代器和生成器/
作者
阿俊
发布于
2020-03-30
许可协议
CC BY-NC-SA 4.0

相关阅读