<!-- TOC -->python
第14章 可迭代的对象、迭代器和生成器python2.7
<!-- /TOC -->code
举个例子:orm
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__
方法。blog
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 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
能够看下这个函数的源码是怎么写的
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第二次,由于每一个生成器只能被使用一次
在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
经过结果能够看到:
总结:生成器是迭代器的一种,但功能方法比迭代器多
生成器的建立可使用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
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