Python3 迭代器与生成器
迭代器
迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器:
实例(Python 3.0+)
>>> it = iter(list) # 创建迭代器对象
>>> print (next(it)) # 输出迭代器的下一个元素
1
>>> print (next(it))
2
>>>
迭代器对象可以使用常规for语句进行遍历:
实例(Python 3.0+)
执行以上程序,输出结果如下:
1 2 3 4
也可以使用 next() 函数:
实例(Python 3.0+)
执行以上程序,输出结果如下:
1 2 3 4
创建一个迭代器
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 。
如果你已经了解的面向对象编程,就知道类都有一个构造函数,Python 的构造函数为 __init__(), 它会在对象初始化的时候执行。
更多内容查阅:Python3 面向对象
__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。
__next__() 方法(Python 2 里是 next())会返回下一个迭代器对象。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
实例(Python 3.0+)
执行输出结果为:
1 2 3 4 5
StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。
在 20 次迭代后停止执行:
实例(Python 3.0+)
执行输出结果为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
生成器
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
当在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。
然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。
调用一个生成器函数,返回的是一个迭代器对象。
下面是一个简单的示例,展示了生成器函数的使用:
实例
以上实例中,countdown 函数是一个生成器函数。它使用 yield 语句逐步产生从 n 到 1 的倒数数字。在每次调用 yield 语句时,函数会返回当前的倒数值,并在下一次调用时从上次暂停的地方继续执行。
通过创建生成器对象并使用 next() 函数或 for 循环迭代生成器,我们可以逐步获取生成器函数产生的值。在这个例子中,我们首先使用 next() 函数获取前三个倒数值,然后通过 for 循环获取剩下的两个倒数值。
生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。
执行以上程序,输出结果如下:
5 4 3 2 1
以下实例使用 yield 实现斐波那契数列:
实例(Python 3.0+)
执行以上程序,输出结果如下:
0 1 1 2 3 5 8 13 21 34 55
cowpea
115***1355@qq.com
来看一下有yield和没有yield的情况会对生成器了解多点:
第一种:使用 yield
#!/usr/bin/python3 import sys def fibonacci(n,w=0): # 生成器函数 - 斐波那契 a, b, counter = 0, 1, 0 while True: if (counter > n): return yield a a, b = b, a + b print('%d,%d' % (a,b)) counter += 1 f = fibonacci(10,0) # f 是一个迭代器,由生成器返回生成 while True: try: print (next(f), end=" ") except : sys.exit()输出结果:
第二种:不使用 yield
#!/usr/bin/python3 import sys def fibonacci(n,w=0): # 生成器函数 - 斐波那契 a, b, counter = 0, 1, 0 while True: if (counter > n): return #yield a a, b = b, a + b print('%d,%d' % (a,b)) counter += 1 f = fibonacci(10,0) # f 是一个迭代器,由生成器返回生成 while True: try: print (next(f), end=" ") except : sys.exit()输出结果:
第二种没有yield时,函数只是简单执行,没有返回迭代器f。这里的迭代器可以用生成l列表来理解一下:
这里的m就像上面的f一样,是迭代器。
cowpea
115***1355@qq.com
ErikaEmma
463***503@qq.com
参考地址
什么情况下需要使用 yield?
一个函数 f,f 返回一个 list,这个 list 是动态计算出来的(不管是数学上的计算还是逻辑上的读取格式化),并且这个 list 会很大(无论是固定很大还是随着输入参数的增大而增大),这个时候,我们希望每次调用这个函数并使用迭代器进行循环的时候一个一个的得到每个 list 元素而不是直接得到一个完整的 list 来节省内存,这个时候 yield 就很有用。
具体怎么使用 yield 参考:Python yield 使用浅析
以斐波那契函数为例,我们一般希望从 n 返回一个 n 个数的 list:
def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L上面那个 fab 函数从参数 max 返回一个有 max 个元素的 list,当这个 max 很大的时候,会非常的占用内存。
一般我们使用的时候都是这个样子的,比如:
f = iter(fab(1000)) while True: try: print (next(f), end=" ") except StopIteration: sys.exit()这样我们实际上是先生成了一个 1000 个元素的 list:f,然后我们再去使用这个 f。
现在,我们换一个方法:
因为我们实际使用的是 list 的遍历,也就是 list 的迭代器。那么我们可以让这个函数 fab 每次只返回一个迭代器——一个计算结果,而不是一个完整的 list:
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # print b a, b = b, a + b n = n + 1这样,我们每次调用fab函数,比如这样:
for x in fab(1000): print(x)或者 next 函数之类的,实际上的运行方式是每次的调用都在 yield 处中断并返回一个结果,然后再次调用的时候再恢复中断继续运行。
ErikaEmma
463***503@qq.com
参考地址
collector
120***9047@qq.com
对yield的测试结果:
def get(): m = 0 n = 2 l = ['s',1,3] k = {1:1,2:2} p = ('2','s','t') while True: m += 1 yield m yield m ,n ,l ,k ,p it = get() print(next(it)) #1 print(next(it)) #(1, 2, ['s', 1, 3], {1: 1, 2: 2}, ('2', 's', 't')) print(next(it)) #2 print(type(next(it))) #<class 'tuple'>collector
120***9047@qq.com
怀雨
522***18@qq.com
def get(): m = 0 n = 2 l = ['s',1,3] k = {1:1,2:2} p = ('2','s','t') while True: m += 1 yield m yield m ,n ,l ,k ,p it = get() print(next(it)) #1 print(next(it)) #(1, 2, ['s', 1, 3], {1: 1, 2: 2}, ('2', 's', 't')) print(next(it)) #2 print(type(next(it))) #<class 'tuple'>如果再加一句:
所以返回值的类型,应该是当前调用时,yield 返回值的类型。
怀雨
522***18@qq.com
hid4net
hid***t@qq.com
感谢楼上各位的实例,自己写了一个小例程,便于我这样的新手理解。
def myYield_1(): a, i = 'yield', 0 while True: print('before #%d' % i, end=", ") yield a, i print('after #%d' % i, end=", ") i += 1 def myYield_2(): a, i = 'yield_a', 0 b, i = 'yield_b', 0 while True: print('before #%d' % i, end=", ") yield a, i yield b, i print('after #%d' % i, end=", ") i += 1 it1 = iter(myYield_1()) it2 = iter(myYield_2()) for i in range(10): print("next #%d" % i, end=": ") print(next(it1)) print('\n') for i in range(10): print("next #%d" % i, end=": ") print(next(it2))输出是这样的:
next #0: before #0, ('yield', 0) next #1: after #0, before #1, ('yield', 1) next #2: after #1, before #2, ('yield', 2) next #3: after #2, before #3, ('yield', 3) next #4: after #3, before #4, ('yield', 4) next #5: after #4, before #5, ('yield', 5) next #6: after #5, before #6, ('yield', 6) next #7: after #6, before #7, ('yield', 7) next #8: after #7, before #8, ('yield', 8) next #9: after #8, before #9, ('yield', 9) next #0: before #0, ('yield_a', 0) next #1: ('yield_b', 0) next #2: after #0, before #1, ('yield_a', 1) next #3: ('yield_b', 1) next #4: after #1, before #2, ('yield_a', 2) next #5: ('yield_b', 2) next #6: after #2, before #3, ('yield_a', 3) next #7: ('yield_b', 3) next #8: after #3, before #4, ('yield_a', 4) next #9: ('yield_b', 4)hid4net
hid***t@qq.com
really
nin***ally9@gmail.com
参考地址
前文提到:字符串,列表或元组对象都可用于创建迭代器。
字符串(Strings):
普通的旧字符串也是可迭代的。
for s in "hello": print s输出结果为:
列表(Lists):
这些可能是最明显的迭代。
for x in [None,3,4.5,"foo",lambda : "moo",object,object()]: print "{0} ({1})".format(x,type(x))输出结果为:
元组(Tuples):
元组在某些基本方面与列表不同,注意到以下示例中的可迭代对象使用圆括号而不是方括号,但输出与上面列表示例的输出相同。
for x in (None,3,4.5,"foo",lambda : "moo",object,object()): print "{0} ({1})".format(x,type(x))输出结果为:
字典(Dictionaries):
字典是键值对的无序列表。当您使用for循环遍历字典时,您的虚拟变量将使用各种键填充。
d = { 'apples' : 'tasty', 'bananas' : 'the best', 'brussel sprouts' : 'evil', 'cauliflower' : 'pretty good' } for sKey in d: print "{0} are {1}".format(sKey,d[sKey])输出结果为:
也许不是这个顺序,字典是无序的!!!
really
nin***ally9@gmail.com
参考地址
wuguandong
963***591@qq.com
使用自定义迭代器实现斐波那契数列
class Fibonacci: def __init__(self, count): self.count = count def __iter__(self): self.i = 0 self.a, self.b = 0, 1 return self def __next__(self): if self.i < self.count: self.i += 1 a_old = self.a self.a, self.b = self.b, self.a + self.b return a_old else: raise StopIteration for i in Fibonacci(10): print(i, end=" ")wuguandong
963***591@qq.com
闫伟超
yif***chaoran@163.com
如教程所说,迭代器和生成器算是 Python 一大特色,其核心是基于迭代器协议来的。
而平时我们经常使用的 for in 循环体,本质就是迭代器协议的一大应用。
同时 Python 内置的集合类型(字符、列表、元组、字典)都已经实现了迭代器协议,所以才能使用 for in 语句进行迭代遍历。for in 循环体在遇到 StopIteration 异常时,便终止迭代和遍历。
再说下可迭代、迭代器、生成器三个概念的联系和区别。
1、可迭代概念范围最大,生成器和迭代器肯定都可迭代,但可迭代不一定都是迭代器和生成器,比如上面说到的内置集合类数据类型。可以认为,在 Python 中,只要有集合特性的,都可迭代。
2、迭代器,迭代器特点是,均可以使用 for in 和 next 逐一遍历。
3、生成器,生成器一定是迭代器,也一定可迭代。
至于 Python 中为何要引入迭代器和生成器,除了节省内存空间外,也可以显著提升代码运行速度。
自定义迭代器类示例和说明如下:
class MyIter(): def __init__(self): #为了示例,用一个简单的列表作为需迭代的数据集合,并且私有化可视情况变为其他类型集合 self.__list1=[1,2,3,4] self.__index=0 def __iter__(self): #该魔法方法,必须返回一个迭代器对象,如果self已经定义了__next__()魔法方法,则只需要返回self即可 #因为如上面所述,生成器一定是迭代器 return iter(self.list1) def __next__(self): #此处的魔法函数,python会自动记忆每次迭代的位置,无需再使用yield来处理 #在使用next(obj)时,会自动调用该魔法方法 res=self.__list1[self.__index] self.__index+=1 return res以上为自定义迭代器类的机制。
下面再示例说明下,如何自定义生成器函数,因为大多数实战场景中,使用生成器函数可能会更多一些:
调用以上函数时,会返回一个生成器对象,然后对该生成器对象,使用 next() 逐一返回:
其实核心的概念还是记忆上次迭代的位置,类中直接使用 __next__ 魔法方法实现,函数中使用 yield 实现。且怀疑,类中的 __next__ 魔法方法底层也是使用 yield 来实现的。
迭代器和生成器具体应用场景,就凡是需要提升运行效率或节约内存资源,且遍历的数据是集合形式的,都可以考虑。
另外一个小众的使用场景,是变相实现协程的效果,即在同一个线程内,实现不同任务交替执行
def mytask1(): print('task1 开始执行') ''' task code ''' yield def mytask2(): print('task2 开始执行') ''' task code ''' yield gene1=mytask1() gene2=mytask2() for i in range(100): next(gene1) next(gene2)闫伟超
yif***chaoran@163.com
仍旧
ren***nfj@163.com
参考地址
Python 中 yield 的用法很像 return,都是提供一个返回值,但是 yield 和 return 的最大区别在于,return 一旦返回,则代码段执行结束,但是 yield 在返回值以后,会交出 CUP 的使用权,代码段并没有直接结束,而是在此处中断,当调用 send() 或者 next() 方法之后,yield 可以从之前中断的地方继续执行。
在一个函数中,使用 yield 关键字,则当前的函数会变成生成器。
下面生成一个斐波那契数列。
def fib(n): index = 0 a = 0 b = 1 while index < n: yield b a,b = b, a+b index += 1生成器对象:
打印出来的结果是一个生成器对象,并没有直接把我们想要的值打印出来。
仍旧
ren***nfj@163.com
参考地址
zbh
122***6113@qq.com
这两段代码是 Python 生成器函数中的重要部分,用于实现斐波那契数列的生成。以下是对这两段代码的详细解释:
yield a:yield是 Python 中一个非常有用的关键字,它用于定义生成器函数并返回生成器对象。当生成器函数执行到yield关键字时,它将将当前函数状态保存为暂停状态,并向调用方返回一个值a,之后程序流程将被挂起,直到下次通过next()函数调用该生成器对象时再恢复执行状态。在斐波那契数列生成器函数中,每次执行到yield a时,都会生成当前数列的第一个数字a并返回给调用方。a, b = b, a + b: 这是 Python 中的一种元组赋值语法,可以同时将多个变量赋值为多个值。在斐波那契数列生成器函数中,这行代码的作用是更新相邻两个数字a和b的值,以便生成下一个斐波那契数。首先,变量a被赋值为变量b的值,表示将上一个斐波那契数列中的第二个数字赋值为下一个数列的第一个数字;然后,变量b被赋值为表达式a + b的值,表示将上一个斐波那契数列中的前两个数字相加得到下一个数列中的第二个数字。这样,在每次执行yield a之前,都会先更新a和b的值,从而生成下一个斐波那契数。zbh
122***6113@qq.com