门户网站建设运行环境要求,上海创意型网站建设,微网站搭建,知名的crm管理系统原标题#xff1a;史上最全 Python 迭代器与生成器作者#xff1a;浪子燕青链接#xff1a;http://www.langzi.fun/迭代器与生成器.html迭代器与可迭代对象概念迭代器#xff1a;是访问数据集合内元素的一种方式#xff0c;一般用来遍历数据#xff0c;但是他不能像列表一…原标题史上最全 Python 迭代器与生成器作者浪子燕青链接http://www.langzi.fun/迭代器与生成器.html迭代器与可迭代对象概念迭代器是访问数据集合内元素的一种方式一般用来遍历数据但是他不能像列表一样使用下标来获取数据也就是说迭代器是不能返回的。Iterator迭代器对象必须要实现next魔法函数Iterable可迭代对象继承Iterator必须要实现iter魔法函数比如fromcollections importIterable,Iteratora [ 1, 2, 3]print(isinstance(a,Iterator))print(isinstance(a,Iterable))返回结果FalseTrue在Pycharm中使用altb进去list的源码中可以看到在list类中有iter魔法函数也就是说只要实现了iter魔法函数那么这个对象就是可迭代对象。上面的例子中a是一个列表也是一个可迭代对象那么如何才能让这个a变成迭代器呢使用iter()即可。fromcollections importIterable,Iteratora [ 1, 2, 3]a iter(a)print(isinstance(a,Iterator))print(isinstance(a,Iterable))print(next(a))print( ----)forx ina:print(x)返回结果TrueTrue1----23可以看到现在a是可迭代对象又是一个迭代器说明列表a中有iter方法该方法返回的是迭代器这个时候使用next就可以获取a的下一个值但是要记住迭代器中的数值只能被获取一次。梳理迭代器(Iterator)与可迭代对象(Iterable)的区别可迭代对象继承迭代器对象可以用for循环(说明实现了iter方法)迭代器对象可以用next获取下一个值(说明实现了next方法)但是每个值只能获取一次单纯的迭代器没有实现iter魔法函数所以不能使用for循环只要可以用作for循环的都是可迭代对象只要可以用next()函数的都是迭代器对象列表,字典,字符串是可迭代对象但是不是迭代器对象如果想变成迭代器对象可以使用iter()进行转换Python的for循环本质上是使用next()进行不断调用for循环的是可迭代对象可迭代对象中有iter魔法函数可迭代对象继承迭代器对象迭代器对象中有next魔法函数一般由可迭代对象变迭代器对象可迭代对象可迭代对象每次使用for循环一个数组的时候本质上会从类中尝试调用iter魔法函数如果类中有iter魔法函数的话会优先调用iter魔法函数当然这里切记iter方法必须要返回一个可以迭代的对象不然就会报错。如果没有定义iter魔法函数的话会创建一个默认的迭代器该迭代器调用getitem魔法函数如果你没有定义iter和getitem两个魔法函数的话该类型就不是可迭代对象就会报错。比如classs:def__init__(self,x):self.x xdef__iter__(self):returniter(self.x)# 这里必须要返回一个可以迭代的对象# def __getitem__(self, item):# return self.x[item]# iter和getitem其中必须要实现一个a s( 123)# 这里的a就是可迭代对象# 这里不能调用next(a)方法因为没有定义forx ina:print(x)这里把注释符去掉返回结果也是一样的返回结果123迭代器对象一开始提起iter搭配Iterable做可迭代对象next搭配Iterator做迭代器。next()接受一个迭代器对象作用是获取迭代器对象的下一个值迭代器是用来做迭代的只会在需要的时候产生数据。和可迭代对象不同可迭代对象一开始是把所有的列表放在一个变量中然后用getitem方法不断的返回数值getitem中的item就是索引值。但是next方法并没有索引值所以需要自己维护一个索引值方便获取下一个变量的位置。classs:def__init__(self,x):self.x x# 获取传入的对象self.index 0# 维护索引值def__next__(self):try:result self.x[self.index]# 获取传入对象的值exceptIndexError:# 如果索引值错误raiseStopIteration# 抛出停止迭代self.index 1# 索引值1用来获取传入对象的下一个值returnresult# 返回传入对象的值a s([ 1, 2, 3])print(next(a))print( ----------)forx ina:# 类中并没有iter或者getitem魔法函数不能用for循环会报错print(x)返回结果Traceback (most recent call last):1----------File C:/CODE/Python进阶知识/迭代协议/迭代器.py, line 34, inforx ina:TypeError: sobject isnotiterable上面一个就是完整的迭代器对象他是根据自身的索引值来获取传入对象的下一个值并不是像可迭代对象直接把传入对象读取到内存中所以对于一些很大的文件读取的时候可以一行一行的读取内容而不是把文件的所有内容读取到内存中。这个类是迭代器对象那么如何才能让他能够使用for循环呢那就让他变成可迭代对象只需要在类中加上iter魔法函数即可。classs:def__init__(self,x):self.x x# 获取传入的对象self.index 0# 维护索引值def__next__(self):try:result self.x[self.index]# 获取传入对象的值exceptIndexError:# 如果索引值错误raiseStopIteration# 抛出停止迭代self.index 1# 索引值1用来获取传入对象的下一个值returnresult# 返回传入对象的值def__iter__(self):returnselfa s([ 1, 2, 3])print(next(a))print( ----------)forx ina:print(x)返回结果1----------23可以看到这个时候运行成功但是这个对象还是属于迭代器对象因为在next获取下一个值会报错。知识整理根据上面的代码提示得到规律iter让类变成可迭代对象next让类变成迭代器(要维护索引值)。可迭代对象可以用for循环迭代器可以用next获取下一个值。迭代器如果想要变成可迭代对象用for循环就要在迭代器内部加上iter魔法函数可迭代对象如果想要能用next魔法函数使用自身类中的iter()方法即可变成迭代器对象classs:def__init__(self,x):self.x xself.index 0def__next__(self):try:result self.x[self.index]exceptIndexError:raiseStopIterationself.index 1returnresultclassb:def__init__(self,x):self.x xdef__iter__(self):returns(self.x)a b([ 1, 2, 3])forx ina:print(x)返回结果123这个时候是不能再用next方法了应为类b是一个可迭代对象并非迭代器这个时候不能用next方法但是可以让类b继承类s这样就能用next()方法获取下一个值但是你的类b中要存在索引值不然会报错如下代码:classs:def__init__(self,x):self.x x# 获取传入的对象self.index 0# 维护索引值def__next__(self):try:result self.x[self.index]# 获取传入对象的值exceptIndexError:# 如果索引值错误raiseStopIteration# 抛出停止迭代self.index 1# 索引值1用来获取传入对象的下一个值returnresult# 返回传入对象的值# def __iter__(self):# return selfclassb(s):def__init__(self,x):self.x xself.index 0def__iter__(self):returns(self.x)a b([ 1, 2, 3])print(next(a))print(next(a))返回结果12可以这么做但是没必要因为这样违反了设计原则。迭代器的设计模式迭代器模式提供一种方法顺序访问一个聚合对象中的各种元素而又不暴露该对象的内部表示。迭代器的设计模式是一种经典的设计模式根据迭代器的特性(根据索引值读取下一个内容不一次性读取大量数据到内存)不建议将next和iter都写在一个类中去实现。新建一个迭代器用迭代器维护索引值返回根据索引值获取对象的数值新建另一个可迭代对象使用iter方法方便的循环迭代器的返回值。生成器生成器函数中只要有yield这个函数就会变成生成器。每次运行到yield的时候函数会暂停并且保存当前的运行状态返回返回当前的数值并在下一次执行next方法的时候又从当前位置继续往下走。简单用法举个例子defgen():yield1# 返回一个对象这个对象的值是1defret():return1# 返回一个数字1g gen()r ret()print(g,r)print(next(g))返回结果 11可以看到return是直接返回数值1yield是返回的一个生成器对象这个对象的值是1使用next(g)或者for x in g:print x 都是可以获取到他的内容的这个对象是在python编译字节码的时候就产生。defgen():yield1yield11yield111yield1111yield11111yield111111# 返回一个对象这个对象内的值是1和11,111...defret():return1return3# 第二个return是无效的g gen()r ret()print(g,r)print(next(g))forx ing:print(x)返回结果 1111111111111111111111就像迭代器的特性一样获取过一遍的值是没法再获取一次的并且不是那种一次把所有的结果求出放在内存或者说不是一次性读取所有的内容放在内存中。梳理特性使用yield的函数都是生成器函数可以使用for循环获取值也可以使用next获取生成器函数的值原理函数工作原理函数的调用满足“后进先出”的原则也就是说最后被调用的函数应该第一个返回函数的递归调用就是一个经典的例子。显然内存中以“后进先出”方式处理数据的栈段是最适合用于实现函数调用的载体在编译型程序语言中函数被调用后函数的参数返回地址寄存器值等数据会被压入栈待函数体执行完毕将上述数据弹出栈。这也意味着一个被调用的函数一旦执行完毕它的生命周期就结束了。python解释器运行的时候会用C语言当中的PyEval_EvalFramEx函数创建一个栈帧所有的栈帧都是分配再堆内存上如果不主动释放就会一直在里面。Python 的堆栈帧是分配在堆内存中的理解这一点非常重要Python 解释器是个普通的 C 程序所以它的堆栈帧就是普通的堆栈。但是它操作的 Python 堆栈帧是在堆上的。除了其他惊喜之外这意味着 Python 的堆栈帧可以在它的调用之外存活。(FIXME: 可以在它调用结束后存活)这个就是生成器的核心原理实现。Python脚本都会被python.exe编译成字节码的形式然后python.exe再执行这些字节码使用dis即可查看函数对象的字节码对象。importdis# 查看函数程序字节码a langziprint(dis.dis(a))print( -* 20)defsb(admin):print(admin)print(dis.dis(sb))返回结果10LOAD_NAME 0(langzi)# 加载名字 为langzi2RETURN_VALUE# 返回值None--------------------150LOAD_GLOBAL 0( print)# 加载一个print函数2LOAD_FAST 0(admin)# 加载传递参数为admin4CALL_FUNCTION 1# 调用这个函数6POP_TOP# 从栈的顶端把元素移除出来8LOAD_CONST 0( None)# 因为该函数没有返回任何值所以加载的值是none10RETURN_VALUE# 最后把load_const的值返回(个人理解)None代码函数运行的时候python将代码编译成字节码当函数存在yield的时候python会将这个函数标记成生成器当调用这个函数的时候会返回生成器对象调用这个生成器对象后C语言中写的函数会记录上次代码执行到的位置和变量。再C语言中的PyGenObject中有两个值gi_frame(存储上次代码执行到的位置f_lasti的上次代码执行到的变量f_locals),gi_code(存储代码)使用dis也可以获取到上次代码执行的位置和值。举个例子importdisdefgen():yield1yield2return666g gen()# g是生成器对象print(dis.dis(g))print( ** 10)print(g.gi_frame.f_lasti)# 这里还没有执行返回的位置是-1print(g.gi_frame.f_locals)# 这里还没有执行返回的对象是{}next(g)print( ** 10)print(g.gi_frame.f_lasti)print(g.gi_frame.f_locals)返回结果110LOAD_CONST 1( 1)# 加载值为12YIELD_VALUE4POP_TOP126LOAD_CONST 2( 2)8YIELD_VALUE10POP_TOP1312LOAD_CONST 3( 666)14RETURN_VALUENone**********- 1# 因为还没有执行所以获取的行数为 -1{}**********2# 这里开始执行了第一次获取的行数是22对应2 YIELD_VALUE就是前面加载的数值1{}# g.gi_frame.f_locals 是局部变量你都没定义那么获取的结果自然是{}你只需在代码中加上useradmin这里的{}就会改变。生成器可以在任何时候被任何函数恢复执行因为它的栈帧实际上不在栈上而是在堆上。生成器在调用调用层次结构中的位置不是固定的也不需要遵循常规函数执行时遵循的先进后出顺序。因为这些特性生成器不仅能用于生成可迭代对象还可以用于实现多任务协作。就是说只要拿到了这个生成器对象就能对这个生成器对象进行控制比如继续执行暂停等待这个就是协程能够执行的理论原理。应用场景读取文件使用open(‘xxx’).read(2019)//打开一个文件每次读取2019个偏移量。文件a.txt是一行文字但是特别长这一行文字根据|符号分开如何读取写入文件代码# -*- coding:utf-8 -*-importrandomimportthreadingimportstringimporttimet1 time.time()defwrite(x):withopen( a.txt, a) asa:a.write(x ||)defrun():forx inrange( 10000000):strs str(random.randint( 1000, 2000)) random.choice(string.ascii_letters)* 10write(strs)forx inrange( 10):t threading.Thread(targetrun)t.start()t2 time.time()print(t2 - t1)读取文件代码# -*- coding:utf-8 -*-defreadbooks(f, newline):# f为传入的文件名newline为分隔符buf # 缓存处理已经读出来的数据量while1:whilenewline inbuf:# 缓存中的数据是否存在分隔符pos buf.index(newline)# 如果存在就找到字符的位置,比如0或者1或者2yieldbuf[:pos]# 暂停函数返回缓存中的从头到字符的位置buf buf[pos len(newline):]# 缓存变成了字符的位置到末尾chunk f.read( 2010* 10)# 读取2010*10的字符ifnotchunk:# 已经读取到了文件结尾yieldbufbreakbuf chunk# 加到缓存withopen( a.txt, r) asf:forline inreadbooks(f, ||):print(line)●编号635输入编号直达本文返回搜狐查看更多责任编辑