一、迭代器
1.1什么是可迭代对象
在之前我们常用for循环来遍历基础数据类型,能被for循环遍历的对象叫做可迭代对象,字符串、列表、元组、字典、集合都可以被称作可迭代对象。
要判断一个对象是否未可迭代对象有两种方法,判断对象内部是否含有__iter__方法,2调用模块直接判断对象是否为可迭代对象
from collections import Iterable l = ['1', 2, 3, 4] t = (1, 2, 3, 4) d = {1: 2, 3: 4} s = {1, 2, 3, 4} print('__iter__' in dir(l)) print('__iter__' in dir(t)) print('__iter__' in dir(d)) print('__iter__' in dir(s)) print(isinstance(l, Iterable)) print(isinstance(t, Iterable)) print(isinstance(d, Iterable)) print(isinstance(s, Iterable))
1.2迭代器
对象内部含有__iter__方法且含有__next__方法的就是迭代器,前面我们知道了字符串、列表、元组、字典、集合都是可迭代对象,因为他们内部都有__iter__方法,那么他们是否是迭代器?我们判断下他们是否有__next__方法
l = ['1', 2, 3, 4] t = (1, 2, 3, 4) d = {1: 2, 3: 4} s = {1, 2, 3, 4} print('__next__' in dir(l)) print('__next__' in dir(t)) print('__next__' in dir(d)) print('__next__' in dir(s))
大家可以运行上面的代码发现返回结果都是False,说明以上基础数据类型都不是迭代器。那么迭代器和可迭代对象到底有什么区别呢?
两者最大的区别是可迭代对象是不可直接取值的,可迭代器是可以取值的。what?,字符串、列表、元组、字典明明都是可以取值的啊,大家注意如果抛开for循环大家是不是发现就不能取值了,时间上for循环是做了优化,把可迭代对象转化成了迭代器再进行取值的,那么有什么对象是迭代器,答案就是文件读取本身就是一个迭代器
f = open('register', 'w',encoding='utf-8') print('__iter__' in dir(f)) print('__next__' in dir(f))
大家可已运行发现文件两种方法都有,说明文件是一个迭代器,大家用的readline就是取值的方法
下面介绍for循环到底做了什么优化:
l1 = [1,2,3,4,5,6] l2 = iter(l1) print(l2.__next__()) #1 print(l2.__next__()) #2 print(l2.__next__()) #3 print(l2.__next__()) #4 print(l2.__next__()) #5 print(l2.__next__()) #6
首先for循环做的第一步是将可迭代对象变成迭代器,语法iter(object),object为可迭代对象。然后用next方法依次取值。大家可以尝试再以上代码上在加一行代码就会发现报错:
l1 = [1,2,3,4,5,6] l2 = iter(l1) print(l2.__next__()) print(l2.__next__()) print(l2.__next__()) print(l2.__next__()) print(l2.__next__()) print(l2.__next__()) print(l2.__next__())#StopIteration
报错为StopIteration,这是因为迭代器l2的值已经取完了,大家要知道迭代器是单向取值的,每取值一个,前面的值就会销毁,这也是迭代器的优点,例如即使我需要1-10000的数字我也只需一个内存地址来实现迭代器取值即可。
下面在解释下为什么我们在使用for循环的时候则不会报错,其实for循环只是对报错就行了异常处理。我们用while循环来模拟for循环:
l1 = [1,2,3,4,5,6] l2 = iter(l1) while 1: try: print(l2.__next__()) except StopIteration: break #运行结果: 1 2 3 4 5 6
所以for循环的本质是先将可迭代对象变成迭代器,然后循环取值并对异常报错进行处理退出循环实现循环取值的功能。
2、生成器
2.1什么是生成器
生成器的本质其实也是迭代器,生成器是在某些特定情况下我们为了节省内存通过编程来实现迭代器的功能。如何实现生成器,下面我们介绍两种方法:1、通过函数实现生成器。2、生成器表达式
2.2生成器函数
在函数内部代码块中有yied关键字,那么这个函数就是生成器函数,yield的作用是会将函数进程暂停在此并返回值给生成器对象:
def func1(x):
x+=1
yield x
x+=3
yield x
x+=5
yield
g = func1(5)
print(g.__next__()) #6
print(g.__next__()) #9
print(g.__next__()) #None
从上面例子可以看出将生成器对象g每采用next方法,函数就会运行到下一个yied位置暂停并返回yield后面的值给生成器对象。可以看出迭代器是需要可迭代对象进行转化。可迭代对象非常占内存。生成器直接创建,不需要转化,从本质就节省内存。
下面举例说明生成器的优点,有这样一个实例,假如要你生成10w件衣服,你有两种方案,1.一次性生成完毕。2、先将生成的衣服规格型号定好,客户边需要我边生产,这样就出现了以下两种代码:
def cloth1(n): for i in range(n+1): print('衣服%s号' % i) cloth1(100000) def cloth2(n): for i in range(1,n+1): yield '衣服%s号' % i g = cloth2(10000) for i in range(50): print(g.__next__()) for i in range(50): print(g.__next__())
可以看出第一种方案采用for循环一次性打印出所有衣服,而第二张方案采用生产器原理,你要多少我就生成多少而且在内存中永远只占一个内存地址。
2.2.1send用法
上面已经介绍了函数生产器的next方法,下面介绍send方法:
1、send 与next一样,也是对生成器取值(执行一个yield)的方法。
2、send 可以给上一个yield 传值
3、第一次取值永远都是next。
4、最后一个yield 永远也得不到send传的值。
def func1():
count = yield 6
print(count)
count1 = yield 7
print(count1)
yield 8
g = func1()
next(g)
g.send('alex') #将'alex'传给count
g.send('太白') #将'太白'传给count1
#运行结果:
alex
太白