第14章 可迭代的对象、迭代器和生成器
<!-- TOC -->
<!-- /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变成一个可迭代对象,然后使用迭代器来调用元素
使用迭代器好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。
迭代器内存消耗检测
可以看看它的内存占用:
使用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第二次,因为每个生成器只能被使用一次
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。