天津商城网站制作,服务型网站的营销特点,营销师资格证报名官网,网页设计师必备软件大家好#xff0c;小编来为大家解答以下问题#xff0c;python生成器有几种写法#xff0c;python生成器函数例子#xff0c;今天让我们一起来看看吧#xff01; 本文部分参考#xff1a;Python迭代器#xff0c;生成器–精华中的精华 https://www.cnblogs.com/deeper/p…大家好小编来为大家解答以下问题python生成器有几种写法python生成器函数例子今天让我们一起来看看吧 本文部分参考Python迭代器生成器–精华中的精华 https://www.cnblogs.com/deeper/p/7565571.html 一 迭代器和可迭代对象 迭代器是访问集合元素的一种方式。火车头采集器AI伪原创。迭代器只能往前不会后退。迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素仅仅在迭代到某个元素时才计算该元素而在这之前或之后元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合比如几个G的文件。 特点 a访问者不需要关心迭代器内部的结构仅需通过next()方法或不断去取下一个内容 b不能随机访问集合中的某个值 只能从头到尾依次访问 c访问到一半时不能往回退 d便于循环比较大的数据集合节省内存 e也不能复制一个迭代器。如果要再次或者同时迭代同一个对象只能去创建另一个迭代器对象。 enumerate()的返回值就是一个迭代器我们以enumerate为例 a enumerate([a,b])for i in range(2): #迭代两次enumerate对象for x, y in a:print(x,y)print(.center(50,-))结果 0 a
1 b
-----------------------分割线------------------------
-----------------------分割线------------------------可以看到再次迭代enumerate对象时没有返回值。 我们可以用linux的文件处理命令vim和cat来理解一下 a) 读取很大的文件时vim需要很久cat是毫秒级因为vim是一次性把文件全部加载到内存中读取而cat是加载一行显示一行 b) vim读写文件时可以前进后退可以跳转到任意一行而cat只能向下翻页不能倒退不能直接跳转到文件的某一页因为读取的时候这个“某一页“可能还没有加载到内存中。 正式进入python迭代器之前我们先要区分两个容易混淆的概念可迭代对象(Iterable)和迭代器(Iterator)。 1.1可迭代对象 定义迭代器是一个对象不是一个函数。只要它定义了可以返回一个迭代器的__iter__方法或者定义了可以支持下标索引的__getitem__方法那么它就是一个可迭代对象。 注意 集合数据类型如list、tuple、dict、set、str都是可迭代对象Iterable却不是迭代器Iterator。 如何判断一个对象是可迭代对象呢可以通过collections模块的Iterable类型判断 from collections import Iterable
print(isinstance([], Iterable))
print(isinstance({}, Iterable))
print(isinstance(abc, Iterable))
print(isinstance({name:join,age:23},Iterable))
print(isinstance(set([2,3]),Iterable))结果为 True
True
True
True
True再看 from collections import Iterator
print(isinstance([], Iterator))
print(isinstance({}, Iterator))
print(isinstance(abc, Iterator))
print(isinstance({name:join,age:23},Iterator))
print(isinstance(set([2,3]),Iterator))结果为 False
False
False
False
False1.2迭代器 定义任何实现了__iter__()和__next__()python2中实现next()方法的对象都是迭代器__iter__返回迭代器自身__next__返回容器中的下一个值。 这里我们来看一下迭代器和可迭代对象的使用方法。 问题不适用for,while以及下表索引怎么遍历一个list l1[2,3,5]
iter_l1 l1.__iter__()
# 或者iter(iter_l1)
# iter(iter_l1)是python的内置函数
# l1.__iter__()调用的是l1对象的__iter__()方法。
# 下面的next()函数和__iter__()函数类似。
print(next(iter_l1)) # 或print(iter_l1.__next__()
print(next(iter_l1))
print(next(iter_l1))
print(next(iter_l1))结果为 2
3
5
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
ipython-input-126-eb58865cb644 in module4 print(next(iter_l1))5 print(next(iter_l1))
---- 6 print(next(iter_l1))结论思路就是先得到当前对象当前对象是可迭代对象的迭代器然后每次执行next(iter_l1)就可以得到当前对象的一个值。 修改一下 l1[2,3,5]
iter_l1 l1.__iter__()
for i in iter_l1:print(i,end,)结果为 2,3,5,再次执行for语句如下 for i in iter_l1:print(i,end,)此次返回结果为空可以看到迭代器只能遍历一次对象不能重复遍历。由于for语句可以自动处理StopIteration异常所以这里没有报出StopIteration而是没有任何结果。 看一个例子。我们想得到Fibonacci数列思路是定义一个可迭代对象类Fib然后在定义一个迭代器类FibIterator使用方式是 ① 实例化一个可迭代对象 fib Fib() ② 得到fib的一个迭代器 fib_iter iter(fib) ③ 然后每次调用next(fib_iter)可得到fibonacci数列中的一个数。 class FibIterator():定义迭代器类def __init__(self,num,a,b,current):self.num numself.a aself.b bself.current currentdef __iter__(self):return selfdef __next__(self):if(self.num-10):self.num self.num-1self.current self.aself.a self.bself.b self.bself.current #以上两步赋值操作可省略中间变量直接写为self.a,self.b self.b,self.aself,b return self.currentelse: raise StopIterationclass Fib:定义可迭代对象所属类def __init__(self,num): #num表示该数列的长度self.a 1self.b 2self.currentself.aself.num numdef __iter__(self):return FibIterator(self.num,self.a,self.b,self.current)fib Fib(20)
fib_iter iter(fib)
for i in range(20):print(next(fib_iter),end,)结果为 1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,这里定义的Fibonacci类的__init__函数有一个参数num表示一共得到几个fibonacci数。 由于可迭代对象是实现了__iter__()方法的迭代器对象是实现了__iter__()和__next__()方法的能否只定义一个迭代器类呢 试一下。 class Fib:def __init__(self,num):self.num numself.a 1self.b 2self.current self.adef __iter__(self):return selfdef __next__(self):if(self.num-10):self.num self.num-1self.current self.aself.a self.bself.b self.bself.current #以上两步赋值操作可省略中间变量直接写为self.a,self.b self.b,self.aself,b return self.currentelse: raise StopIterationfib Fib(10)
for i in fib:print(i,end,)结果为 1,2,3,5,8,13,21,34,55,89,当我们再次执行for语句时 for i in fib:print(i,end,)同上面的分析一样结果为空。如果想再次得到fibonacci数列的前10个数就必须重新实例化Fib对象。 结论为什么不只保留Iterator的接口而还需要设计Iterable呢 因为迭代器迭代一次以后就空了那么如果listdict也是一个迭代器迭代一次就不能再继续被迭代了这显然是反人类的所以通过__iter__每次返回一个独立的迭代器就可以保证不同的迭代过程不会互相影响。 另外迭代器是惰性的只有在需要返回下一个数据时它才会计算。所以Iterator甚至可以表示一个无限大的数据流例如全体自然数。而使用list是永远不可能存储全体自然数的。 下面的例子得到全体自然数下面我们用生成器可以得到更简单的写法。 class Natural:def __init__(self):passdef __iter__(self):return NaturalIterator()
class NaturalIterator:def __init__(self):self.beg0self.currentself.begdef __iter__(self):return selfdef __next__(self):self.current 1return self.current # 显示前20个自然数
n1Natural()
n1_iter iter(n1)
for i in range(20):print(next(n1_iter),end,)结果为 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,作业使用迭代器和可迭代对象实现得到全体fibonacci数。 二 生成器 2.1 生成器的定义和使用 定义生成器其实是一种特殊的迭代器它不需要再像上面的类一样写__iter__()和__next__()方法了只需要一个yiled关键字。 生成器一定是迭代器反之不成立。 Python有两种不同的方式提供生成器 生成器函数常规函数定义但是使用yield语句而不是return语句返回结果。 yield语句一次返回一个结果在每个结果中间挂起函数的状态以便下次重它离开的地方继续执行生成器表达式类似于列表推导但是生成器返回按需产生结果的一个对象 而不是一次构建一个结果列表 我们先用一个例子说明一下 def generate_even():i0while True:if i%20:yield ii 1ggenerate_even()
dir(g) # 可以看到里面有__iter__()方法和__next__()方法所以生成器也是迭代器。for i in range(10):print(g.__next__(),end,)结果为 0,2,4,6,8,10,12,14,16,18,现在解释一下上面的代码 我们知道一个函数只能返回一次即return以后这次函数调用就结束了 但是生成器函数可以暂停执行并且通过yield返回一个中间值当生成器对象的__next__()方法再次被调用的时候生成器函数可以从上一次暂停的地方继续执行直到下一次遇到yield语句此时会返回yield后面的值如果有的话或者触发一个StopIteration。 了解协同程序 a) 生成器的另外一个方面是协同程序的概念。协同程序是可以运行的独立函数调用可以暂停或者挂起并从程序离开的地方继续或者重新开始。 b) 可以在调用者和被调用的之间协同程序通信。 c) 在程序暂停时可以传参举例来说当协同程序暂停时我们仍可以从其中获得一个中间的返回值当调用回到程序中时能够传入额外或者改变了的参数但是仍然能够从我们上次离开的地方继续并且所有状态完整。 生成器表达式类似于列表推导式只是把[]换成()这样就创建了一个生成器。 gen (x for x in range(10))下面我们用生成器来实现前面的fibonacci数列和全体自然数。 # 生成前n个fibonacci数
def fib(n):a, b 0, 1count0while True:if(countn):breakcount 1yield ba, b b, abf fib(20)
for item in f:print(item,end,)结果为 1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,def Natural():n0while True:yield nn 1
g_n1Natural()
for i in range(20):print(next(g_n1),end,)结果为 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,2.2 send方法 生成器函数最大的特点是可以接受外部传入的一个变量并根据变量内容计算结果后返回。这是生成器函数最难理解的地方也是最重要的地方协程的实现就全靠它了。 看一个小猫吃鱼的例子 def cat():print(我是一只hello kitty)while True:food yieldif food 鱼肉:yield 好开心else:yield 不开心人家要吃鱼肉啦中间有个赋值语句food yield可以通过send方法来传参数给food试一下 情况1 miao cat() #只是用于返回一个生成器对象cat函数不会执行
print(.center(50,-))
print(miao.send(鱼肉))结果 Traceback (most recent call last):
--------------------------------------------------File C:/Users//Desktop/Python/cnblogs/subModule.py, line 67, in moduleprint(miao.send(鱼肉))
TypeError: cant send non-None value to a just-started generator看到了两个信息 amiao cat() 只是用于返回一个生成器对象cat函数不会执行 bcan’t send non-None value to a just-started generator不能给一个刚创建的生成器对象直接send值 改一下情况2 miao cat()
miao.__next__()
print(miao.send(鱼肉))那么到底send()做了什么呢send()的帮助文档写的很清楚’’‘Resumes the generator and “sends” a value that becomes the result of the current yield-expression.’’’可以看到send依次做了两件事 a回到生成器挂起的位置继续执行 b并将send(arg)中的参数赋值给对应的变量如果没有变量接收值那么就只是回到生成器挂起的位置 但是我认为send还做了第三件事 c兼顾__next__()作用挂起程序并返回值所以我们在print(miao.send(‘鱼肉’))时才会看到’好开心’其实__next__()等价于send(None) 所以当我们尝试这样做的时候 def cat():print(我是一只hello kitty)while True:food yieldif food 鱼肉:yield 好开心else:yield 不开心人家要吃鱼肉啦miao cat()
print(miao.__next__())
print(miao.send(鱼肉))
print(miao.send(骨头))
print(miao.send(鸡肉))就会得到这个结果 我是一只hello kitty
None
好开心
None
不开心人家要吃鱼肉啦我们按步骤分析一下 a执行到print(miao.next())执行cat()函数print了”我是一只hello kitty”然后在food yield挂起并返回了None打印None b接着执行print(miao.send(‘鱼肉’))回到food yield并将’鱼肉’赋值给food生成器函数恢复执行直到运行到yield ‘好开心’程序挂起返回’好开心’并print’好开心’ c接着执行print(miao.send(‘骨头’))回到yield ‘好开心’这时没有变量接收参数’骨头’生成器函数恢复执行直到food yield程序挂起返回None并print None d接着执行print(miao.send(‘鸡肉’))回到food yield并将’鸡肉’赋值给food生成器函数恢复执行直到运行到yield’不开心人家要吃鱼肉啦’程序挂起返回’不开心人家要吃鱼肉啦’并print ‘不开心人家要吃鱼肉啦’ 大功告成那我们优化一下代码 def cat():msg 我是一只hello kittywhile True:food yield msgif food 鱼肉:msg 好开心else:msg 不开心人家要吃鱼啦miao cat()
print(miao.__next__())
print(miao.send(鱼肉))
print(miao.send(鸡肉))我们再看一个更实用的例子一个计数器。 def counter(start_at 0):count start_atwhile True:val yield countif val is not None:count valelse:count 1count counter(5)
print(count.__next__())
print(count.send(0))结果为5,0而不是5,6的原因 ①执行print(count.next()),程序运行到val yield count(第一个yield语句后挂起然后返回yield后面的值所以结果为5。 ②执行print(count.send(0))程序恢复到挂起点val yield count,将send的参数0赋值给接受变量val,然后继续执行下面的语句由于val0所以if val is not None条件为真count val,接着又来到yield语句val yield count,此时程序挂起返回yield后面的值count0。 综上当执行next()方法时程序会恢复到挂起点依次执行yield语句下面的语句最后再返回yield后面的值而不是先返回yield后面的值再执行yield后面的语句。 最后给出一张图说明Iterable,Iterator,Generator的关系 补充几个小例子 a使用生成器创建一个range def range(n):count 0while count n:yield countcount 1