迭代器
2020年7月1日
迭代是数据处理的基石,内存放不下数据 时,需要找到一种 惰性获取数据项 的方式,即按需一次获取一个数据项,这就是 迭代器模式(Iterator pattern)
1. 什么是迭代器
通常,迭代器是从 集合 中取元素,表示集合是有限多个,只是通过迭代器来一个个取
既然是取集合,那么所有集合都 可迭代,场景如下
- 可迭代意味着能 for 循环遍历
- 可构建和扩展集合类型
- 逐行遍历文本文件
- 列表、字典、集合推导
- 元组拆包
- 调用函数时,使用拆包实例
1.1 可迭代对象(单词序列)
实现 Sentence 类: 向该类的构造方法中,传入一个包含某些文本的字符串,然后可以实现逐个单词的迭代
- 首先,实现一个类,该类包含了 序列协议
- 它的实例化对象可迭代(能像序列一样,使用 for 循环 遍历出结果)
import re
import reprlib
RE_WOED = re.compile("\w+")
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WOED.findall(text)
def __getitem__(self, index):
return self.words[index]
def __repr__(self):
return f"Sentence{reprlib.repr(self.text)}"
s = Sentence("hello word!")
for word in s:
print(word)
print(list(s))
### 输出结果:
# hello
# word
# ['hello', 'word']
默认情况下 reprlib.repr() 生成的字符串最多有 30 个字符,此处给
__repr__
使用此时会发现 Sentence 的实例化对象 s 可直接循环并打印出 word
这是由于 for 循环每次迭代的时候会直接去
__getitem__()
取
1.2 迭代器
什么是迭代器
利用内置方法 iter() 把 list 、 dict 、 str 等 Iterable(可迭代对象) 进行转换,返回的对象 Iterator
生成器都是迭代器,迭代器不一定都是生成器
title = ['Python','Java','C++'] # 列表是一个可迭代对象
isinstance(title, Iterable) # True
a = iter(title) # 由可迭代对象的 iter 方法返回一个迭代器
>>> next(a)
Python
>>> next(a)
Java
>>> next(a)
C++
>>> next(a) # 抛出 StopIteration 异常
Iterator 这个对象就是一个迭代器对象,也就是迭代器了
str list tuple dict:Iterable (可迭代对象)
什么是迭代器?(迭代器协议)
Iterable定义了可返回迭代器的__iter__()方法、__next__() 方法
1.有iter方法:__iter__()
2.有next方法:__next__()
为什么必须有iter方法?
首先这是一个规定:好多iter方法return的是self(自身)(这里是说__iter__()内部return self 见下:for循环的第一件事)
但想for循环只有next方法没有iter方法就不能进行循环(就是自定义迭代器没有iter方法的话如何循环)
因此内置的iter方法实际是调用__iter__()方法 如int类自身没有__iter__()方法则无法调用
'int' object is not iterable
迭代器调用next()方法调用做的两件事:
1.为下一次调用next()方法修改状态
2.生成当前调用的返回结果
生成器比迭代器更优雅 因为是用yield实现的(满足迭代器协议 本身也是一个迭代器对象)
for循环探讨后续:(三件事)
已知for循环in后面接的是可迭代对象 但是可迭代对象并不具有iter方法 如:
[1,2,34] 我们不能把它next([1,2,34]) 因为它是list对象啊
1.因此for循环 第一件干的事就是把 “可迭代对象变成迭代器” ---用了iter()方法(实质上是去找可迭代对象里的__iter__方法,当有for循环时
便会自动执行对象中的__iter__方法,此方法只会返回迭代器,详情见4.3特殊方法)
2.第二件干的事就是不断调用迭代器对象的next方法进行迭代
3.第三便是前面说的捕获异常并处理StopIteration
isinstance(o,t):
判断前一个对象是不是后面的类型 返回True False
导入collections中的Iterator和Iterable模块便可进行辨析 主要见代码
现在在来看文件:
f = open("xx.txt",r+,encoding="utf8")
for i in f.readlines(): 此时是把文件复制之后每一行当成一个元素放到列表中存储,再返回
若利用for i in f:则压根没有复制f 而是把f直接利用iter(f)返回了迭代器对象,之后f.read()
每次调用(next)时才占一行的内存 因此这就是不用readlines的原因(迭代器不占内存的好处,用时next一条)
for的实质:
生成器可以用for i in s: print(i)这样来取出 这种方式看似没有用到next()内置方法
i能取到s的值是for的功能 for就是对s内部进行了一个next的调用(for循环遍历可迭代对象)
python的for里面到底做了什么? 其实它就是做了这么一件事情 调用里面的next() 取里面的值(不断的next赋值给i)
之后每次调用新的值赋给了i 之前的便会被垃圾回收 因此空间就出来了(因此占内存的只有这一个数,空间永远自由)
按理来说调用到最后一个会报异常 然而for会进行检测。 for是利用了异常捕获(except)捕捉到异常(迭代停止)直接
就返回了 不做任何其他处理
while True:
i = next(可迭代对象)
捕捉异常 进行处理(返回)
容器、迭代器、生成器区别: 1.容器:包含常见的列表、元组、字典、集合和字符串,序列存储在内存中,需要的时候可以一并取出
2.迭代器:iter(容器)返回的对象,按需存储,可以通过next()进行迭代,但并不是把所有序列放在内存中再迭代取值,而是仅仅将迭代到的某个值取到内存中
3.生成器:算是另一个迭代器,不仅可以迭代按需取数据,还可以通过send()传入数据,并在生成器内部计算
相同点:都是可迭代对象
按位置迭代
from itertools import islice
def foo():
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
for i in li:
yield i
if __name__ == '__main__':
f = foo()
limit = 10
offset = 10
for i in islice(f, limit, limit + offset):
print(i)
使用异步形式
$ pip install aioitertools
计算长度(通常来讲,不应计算迭代器的总长度,而是用来省内存 )
# 会改变迭代器的下次迭代位置,需再 new 个对象
iter = (i for i in range(50))
sum(1 for _ in iter)
# ------------
import more_itertools
iter5 = iter([1, 2, 3, 4, 5, 6])
print(more_itertools.ilen(iter5))