第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'])TodayisTuesday我们知道一个对象可以迭代是因为实现了__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变成一个可迭代对象,然后使用迭代器来调用元素
使用迭代器好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。

迭代器内存消耗检测
可以看看它的内存占用:
使用python2.7.15+:
import sysi = 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)结果返回:
56800006432使用python 3.6+
import sysi = iter(range(1000000))print (sys.getsizeof(i))
r = range(1000000)print (sys.getsizeof(r))返回结果:
3248这里有个问题:为什么在python2跟Python3的运行结果相差这么大呢? 这是因为python3内部机制已经将range转换成了一个迭代器了。
这里可以看的出来适合大的数据,比如好几个G的数据, 使用了迭代器 内存使用大幅度减少,这是迭代器最大的优点。
总结下: 我们简单说迭代器就是访问集合元素,迭代器就是有一个next()方法的对象,而不是通过索引来计数的。
迭代器使用
那么我们怎么能访问迭代器里面的元素呢?
迭代器有两个方法 ,分别是**iter()和next()**方法
这两个方法是迭代器最基本的方法 一个用来获得迭代器对象,一个用来获取容器中的下一个元素。
itertools是python提供的非常高效创建与使用迭代器的模块
from itertools import chaintest = 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 A1 B2 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= 0i= 1i= 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 Zrangebefore yield: 00----------after yield: 1before yield: 11----------after yield: 2before yield: 22通过结果可以看到:
- 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。
- 当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())) # Trueprint(isgeneratorfunction(demo())) # Falseprint(isgeneratorfunction(demo)) # Trueyield与协程
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:firstprint(c.send("second")) # result:second
c.close()
# c.send("hello") # StopIteration