python进阶之生成器
迭代器
什么叫迭代
可以被for循环的就说明他们是可迭代的,比如:字符串,列表,字典,元祖,们都可以for循环获取里面的数据
下面我们看一个代码:
1 number = 12345 2 for i in number: 3 print(i) 4 输出: 5 Traceback (most recent call last): 6 File "D:**.py", line 272, in <module> 7 for i in number: 8 TypeError: 'int' object is not iterable
报错信息是说:int类型不可迭代,不能使用循环取每个数据。 那么我们又怎么说 字符串,列表,字典,元祖是可迭代的呢?
1 from collections import Iterable 2 3 l = [1, 2, 3, 4] 4 t = (1, 2, 3, 4) 5 d = {1: 2, 3: 4} 6 s = {1, 2, 3, 4} 7 8 print(isinstance(l, Iterable)) # 判断是否是可迭代 9 print(isinstance(t, Iterable)) 10 print(isinstance(d, Iterable)) 11 print(isinstance(s, Iterable))
1 2 3 4 | True True True True |
再从字面上理解一下,其实迭代就是我们刚刚说的,可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代。
什么叫可迭代协议
我们现在是从结果分析原因,能被for循环的就是“可迭代的”,但是如果正着想,for怎么知道谁是可迭代的呢?
假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。
可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__方法。
1 print(dir([1,2])) 2 print(dir((1,1))) 3 print(dir({1:2})) 4 print(dir({1,2}))
输出:
1 2 3 4 | [ '__add__' , '__class__' , '__contains__' , '__delattr__' , '__delitem__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__gt__' , '__hash__' , '__iadd__' , '__imul__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__len__' , '__lt__' , '__mul__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__reversed__' , '__rmul__' , '__setattr__' , '__setitem__' , '__sizeof__' , '__str__' , '__subclasshook__' , 'append' , 'clear' , 'copy' , 'count' , 'extend' , 'index' , 'insert' , 'pop' , 'remove' , 'reverse' , 'sort' ] [ '__add__' , '__class__' , '__contains__' , '__delattr__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__getnewargs__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__len__' , '__lt__' , '__mul__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__rmul__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , 'count' , 'index' ] [ '__class__' , '__contains__' , '__delattr__' , '__delitem__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__len__' , '__lt__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__setitem__' , '__sizeof__' , '__str__' , '__subclasshook__' , 'clear' , 'copy' , 'fromkeys' , 'get' , 'items' , 'keys' , 'pop' , 'popitem' , 'setdefault' , 'update' , 'values' ] [ '__and__' , '__class__' , '__contains__' , '__delattr__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__iand__' , '__init__' , '__init_subclass__' , '__ior__' , '__isub__' , '__iter__' , '__ixor__' , '__le__' , '__len__' , '__lt__' , '__ne__' , '__new__' , '__or__' , '__rand__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__ror__' , '__rsub__' , '__rxor__' , '__setattr__' , '__sizeof__' , '__str__' , '__sub__' , '__subclasshook__' , '__xor__' , 'add' , 'clear' , 'copy' , 'difference' , 'difference_update' , 'discard' , 'intersection' , 'intersection_update' , 'isdisjoint' , 'issubset' , 'issuperset' , 'pop' , 'remove' , 'symmetric_difference' , 'symmetric_difference_update' , 'union' , 'update' ] |
现在我们可以知道:可以被for循环的都是可迭代的,要想迭代内部必须有一个__iter__方法。
那么这个方法又干了些什么事情呢?
1 2 3 4 | print ([ 1 , 2 ].__iter__()) 结果 <list_iterator object at 0x1024784a8 > |
看结果,应该是得到了一个可迭代对象list_iterator 就是一个迭代器,现在我们知道这个列表有一个迭代器了
1 print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 2 3 输出: 4 {'__next__', '__setstate__', '__length_hint__'}
我们获取了列表迭代器3个方法,那么这些方法又干了什么呢? 我们只说__next__
1 iter = [1,2,3,4,5,6].__iter__() 2 3 #一个一个的取值 4 print(iter.__next__()) 5 print(iter.__next__()) 6 输出: 7 1 8 2
我们看到的结果是取到了列表的前两个元素,所以说,for循环就是调用了内部的__next__方法实现遍历的,我们可以不使用for循环,直接调用这个方法就可以实现遍历列表元素
但是如果我们列表有3个元素我们调用__next__4次就会抛出异常StopIteration,因为没有第4个元素
1 iter = [1,2,3].__iter__() 2 3 #一个一个的取值 4 print(iter.__next__()) 5 print(iter.__next__()) 6 print(iter.__next__()) 7 print(iter.__next__()) 8 输出: 9 Traceback (most recent call last): 10 1 11 File "D:/pythonSeleniumTestCode/pythonStu/python练习100例.py", line 295, in <module> 12 2 13 print(iter.__next__()) 14 3 15 StopIteration
现在我们把这个异常处理一下
1 iter = [1,2,3].__iter__() 2 while 1: 3 try: 4 item = iter.__next__() 5 print(item) 6 except StopIteration: 7 break
那现在我们就使用while循环实现了原本for循环做的事情,我们是从谁那儿获取一个一个的值呀?是不是就是l_iter?好了,这个l_iter就是一个迭代器。
迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。
现在我们已经大概有了迭代器的印象,那么我们再来看看生成器是个什么鬼!
生成器
我们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象通过执行iter方法得到的,迭代器有的好处是可以节省内存。
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器
Python中提供的生成器
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator
本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)
特点:惰性运算,开发者自定义
看实例代码:
1 def genrator(): 2 for i in range(1, 5): 3 yield ('正在生成数字{}'.format(i)) # 4 5 yie = genrator() 6 for i in yie: 7 print(i)
输出:
1 2 3 4 | 正在生成数字 1 正在生成数字 2 正在生成数字 3 正在生成数字 4 |
如果我只想生成2个数字我们该怎么实现呢?是不是这样?
1 yie = genrator() 2 num = 0 3 for i in yie: 4 print(i) 5 num+=1 6 if num == 2: 7 break
输出:
1 2 | 正在生成数字 1 正在生成数字 2 |
现在们已经生成了2个数字了,那么我想接着生成,还可不可以呢?
1 def genrator(): 2 for i in range(1, 5): 3 yield ('正在生成数字{}'.format(i)) 4 5 yie = genrator() 6 num = 0 7 for i in yie: 8 print(i) 9 num+=1 10 if num == 2: 11 print('我只能生成2个数') 12 break 13 for i in yie: 14 print(i)
输出:
1 2 3 4 5 | 正在生成数字 1 正在生成数字 2 我只能生成 2 个数 正在生成数字 3 正在生成数字 4 |
结果我们分析出,生成2个数以后既然还可以接着原来的生成。
下面我们再来看看到底怎么使用生成器,我现在要监听一个文件的输入,如果文件中增加了数据,我就在控制到输出增加的内容
import time
def tail(filename):
f = open(filename)
f.seek(0, 2) #从文件末尾算起
while True:
line = f.readline() # 读取文件中新的文本行
if not line:
time.sleep(0.1)
continue
yield line
tail_g = tail('tmp.txt')
for line in tail_g:
print(line)
只要我再A文件中写入一行数据,那么控制到就会输出这行数据,我们就达到了监听文件的作用,是不是还挺好用的!
结论
生成器好处
1.不会占用太多的内存,我们需要生成一个数就生成,不需要就不用叫他生成
2.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。
什么是生成器
只要含有yield关键字的函数都是生成器函数, 且yield不能与return一起使用,二者存一,而且只能写在函数的内部
----------------------------真正的勇士, 敢于直面惨淡的Warning、 敢于正视淋漓的Error--------------------------
版权声明
出处: 博客园Linux超的技术博客--https://www.cnblogs.com/linuxchao/
您的支持是对博主最大的鼓励,感谢您的认真阅读
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
作者: Linux超
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步