1、可迭代的、迭代器、生成器总结
迭代对象、迭代器、生成器的关系:
可迭代对象、可迭代器有什么区别?
- 2者实现的方法不一致,可迭代对象只实现了__iter__方法,可迭代器实现__iter__和__next__方法,后者是前者的子集;
- 内存分配方式不一致,可迭代对象会将全部的元素都放入内存,而可迭代器是随时用随时分配,会记录下当前运行的状态和信息,下一次调用元素时在分配。
2、可迭代的
可迭代的,iterable
直观上来说:序列类、容器类,都是可迭代的。
可迭代的可以理解为:内部实现了__iter__()方法,可通过调用__iter__()方法,取得下一个元素,如果没有下一个元素抛出异常:StopIteration
3、可迭代对象Iterable
定义:可迭代对象,是指内部实现了__iter__()方法的对象,__iter__()方法返回的是迭代对象自身。
判断:可迭代对象可通过instance(x, Iterable)来判断。可迭代对象都可以使用for循环迭代使用,但可使用for循环的未必是可迭代对象,因类中实现了__getitem__方法的对象,也可以被for循环迭代,但并不是可迭代对象。
可迭代对象包括:str、list、tuple、dict、set
基本数据对象中的str、list、tuple、dict、set是可迭代对象,但并不是迭代器,但是可以通过iter(iterable) 方法转换为迭代器。迭代器是可以通过next() 方法,不断的计算下一个迭代对象,直到没有数据时,抛出StopIteration异常。
from collections.abc import Iterable, Iterator
astr = ""
res = isinstance(astr, Iterable)
>> True
res = isinstance(astr, Iterator)
>> False
res = isinstance(iter(astr), Iterator)
>> True
4、迭代器Iterator
定义:可迭代器,是指内部实现了__iter__()和__next__()方法的对象。和可迭代对象相比,可迭代对象包含于迭代器中。__iter__() 返回迭代器对象,如果后面没有迭代对象,会报错StopIteration,__next__() 返回下一个迭代器对象。
判断:迭代器可通过instance(x, Iterator)来判断。
迭代器处理顺序:内部不断的调用__next__() 取得下一个元素,__iter__() 返回迭代器自身;如没有下一个元素,触发StopIteration异常。
5、生成器
迭代可以理解为循环处理,处理的对象比如列表或字典等,会存放在内存空间。但是,如果出现列表长度非常长等情况时,将所有的列表都存放在内存,并不是可取的方案,而生成器,可以解决这个问题:迭代处理,同时可做到随时用随时分配内存空间。
有2种方式可以得到生成器:1)列表生成器;2)带yield的函数
5.1、列表生成器
列表生产器的[] 修改为 (),如下面例子中,如果是[],mark只是一个列表,但当把[]改为()后,mark成了一个生成器。在输出mark,得到的是生成器。
mark = (x for x in [1, 2, 3, 4, 5])
>> <generator object <genexpr> at 0x10d69d580>
以上列表生成器,可以理解为,对列表[1, 2, 3, 4, 5]的迭代,每次只取一个元素使用,并保存当前的迭代位置,用以下次迭代,当没有元素后,会报错StopIteration。
怎样访问列表生成器中的元素?
- next():基本上不会用到next()方法,用for循环来处理,也不用处理StopIteration异常。
- for
# 使用next()得到生成器的下一个元素 mark = [1, 2, 3] print(next(mark)) print(next(mark)) print(next(mark)) print(next(mark)) print(next(mark)) print(next(mark))
>> 1
>> 2
>> 3
>> StopIteration
# 使用for循环来迭代访问生成器中的元素 mark = (x for x in [1, 2, 3, 4, 5]) for i in mark: print(i)
5.2、yield
列表生成器,得到的生成器比较简单。当有较复杂的迭代逻辑时,可用生成器函数来实现。生成器函数是指:一个函数中,有yield关键字。
可以理解为,生成器函数保存的是算法,每次迭代,都会计算下一个元素的值,如果没有下一个元素会抛出异常StopIteration。
5.2.1、和普通函数相比
1)调用方式不一致;next(g)和g.send(value)调用
- 普通函数,func()表示调用函数func,此时会为函数中的数据分配栈空间,函数执行完后,分pop栈空间。
- 生成器函数,g()并没有调用函数,而是生成一个生成器函数,调用生成器需使用next(g)或g.send(value),next(g)是每次返回生成器的下一个元素,g.send(value)是改写了生成器的返回,第1次调用生成器需使用next(g),后面可使用next(g)或g.send(value),因没有1次调用生成器,g.send(value)也不能改写生成器的返回。
2)从不存储任何取值,生成器是一个一个的生成值
def student(): print("for test") m = yield 5 print(m) d = yield 12 s = student() print(next(s)) # 打印结果:for test 5 print(s.send(10)) # 打印结果: 10 12
解析以上例子:
- s = student():并没有调用函数,只是得到一个生成器
- print(next(s)):next(s)执行到yield 5停止,此时yield可以理解为return,遇到yield,会暂停在yield执行的右边,所以左边的赋值给m并没有执行,print(next(5))将打印出5
- print(s.send(10)):再次调用生成器,从上次执行的地方继续执行。首先将取值赋值为m,而此时g.send(10)是将10这个参数直接覆盖之前的结果,将10赋值给m;在执行print(m)将打印出10,最后yiled 12暂停,返回12,print(s.send(10))将返回的12打印出来。
5.2.2、和return相比
- 普通函数return后,函数执行完成,函数的栈空间释放。
- yield,可以理解为生成器函数的返回,但只是暂时返回,将保存生成器函数的结果,下次在调用从上次执行结果处继续执行。