python笔记:迭代器和生成器

本文详细介绍了Python中的迭代器和生成器。迭代器是一种访问集合元素的方式,需要实现`__iter__()`和`__next__()`方法。生成器是特殊的迭代器,通过`yield`关键字实现,允许在循环过程中计算元素,节省内存。生成器适用于数据量大且不需随机访问的情况。此外,还讨论了生成器的工作原理和使用方法,包括`next()`和`send()`方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

迭代器

1.迭代是一种访问集合元素的方式。
2.可迭代对象 : 可以直接作用于for循环的对象(iterable)

常见的可迭代对象有:
1.集合类型:List、Str、dict、 tuple
2.generator:包括生成器和待yield的generator function

判断是否可以迭代: isinstance( [ ], Iterable)
from collections.abc import Iterable
isinstance([], Iterable)
True

3.迭代器:可以被next ( )函数调用并不断返回下一个值的对象(iterator),是一个可以记住遍历位置的对象;迭代器对象从集合的第一个元素开始访问,直到所有的元素访问结束,迭代器只能往前不能往后

创建一个迭代器

1.把一个类作为一个迭代器使用,则类中必须包含两个方法:

a. __ next__(self) : 返回容器的下一个元素
b. __ iter__(self) : 返回一个迭代器(iterator)

2.迭代器都是Iterator对象,但可迭代对象不一定是迭代器。Python中的list、dict、str虽然是可迭代对象,但不是迭代器,可以使用python内置的iter()函数将其转换为一个迭代器。

iter(obj)
myIter = iter([1,2,3])

迭代器迭代的几种方式

a. 通过调用迭代器中的__next__()方法
b. 通过内置的next()方法
c. 常规的for循环进行遍历

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)   # 返回一个迭代器

#迭代方式1
print(myiter.__next__())
print(myiter.__next__())
#迭代方式2
print(next(myiter))  #不断地返回容器中下一个元素
print(next(myiter))
print(next(myiter))
#迭代方式3
for item in myiter:
    if item < 20:
    	print(item)
    else:   
        raise StopIteration

如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法.
类中实现了__iter__(),则该类为可迭代类;类中同时实现__iter__()和__next__(),则该类是一个迭代器类.
在for … in …操作中,第一次调用会先调用iter来获取迭代对象[只调用一次],然后再不断调用对象next

StopIteration
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

Python内建的next函数作用于迭代器上,会执行三个操作:

  • 返回当前『位置』的元素,第一次next调用,当前位置是可迭代对象的起始位置
  • 将『位置』向后递增
  • 如果到达可迭代对象的末尾,即没有元素可以提取,则抛出StopIteration异常

Python中的循环语句会自动进行迭代器的建立、next的调用和StopIteration的处理。for循环本质上就是通过不断调用next()函数实现的

迭代器模式特别适合于以下情形:

a.不关心元素的随机访问
b.元素的个数不可提前预测

生成器

  • 生成器是一种边循环边计算的机制,是一种特殊的迭代器
  • 在 Python 中,使用了 yield 的函数被称为生成器(generator)。
  • 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,调用一个生成器函数,返回的是一个迭代器对象。

生成器的作用

正常情况下,列表所有数据都在内存中,如果数据量较少没有什么问题,但如果数据量特别多就会非常消耗内存。如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间,这就是生成器的作用。

总结:生成器保存的是生成数据的算法,可以在边循环时计算出后续的元素,节省大量的内存空间,数据什么时候需要,什么时候生成。

一个典型的生成器示例就是斐波那契数列的实现:

import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b  #保存数据生成的方式,而不是完整的数据列表
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

生成器的创建方式

【与迭代器的区别之一】
1.将列表生成式的 [ ] 改成 ( ),就创建了一个generator

g = (x*2 for x in range(10))

for item in g:
	print(item)

2.如果一个函数中包含yield关键字,则该函数不再是一个普通的函数而是一个生成器,调用函数就是创建一个生成器对象

def test():
	i = 0
	while i < 5:
		yield i
		i += 1
a = test()
print(next(a))
print(next(a))

0
1

生成器的执行方式

1.通过调用next方法(或__next__()方法)。

G = (x*2 for x in range(10))
print(next(G))

每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

2.通过for循环遍历(使用较多,其本质其实还是调用next方法)

g = (x*2 for x in range(10))

for item in g:
	print(item)

通过 for 循环来迭代它,不需要关心 StopIteration 异常。但是用for循环调用generator时,得不到generator的return语句的返回值。如果想要拿到返回值,必须用next()方法,且捕获StopIteration错误,返回值包含在StopIteration的value中。

生成器的工作原理

生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。

带有 yield 的函数不再是一个普通函数,而是一个生成器【generator】。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,保存当前所有的运行信息。同时,每次中断,yield相当于return返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始继续执行。因此,生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造中的位置

调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象),函数的真正运算需要相应的生成器执行方式

def odd():
    print ('step 1')
    yield 1  #运行的yield处,函数停止,并将yield后的值返回给调用对象
    print ('step 2') #下次迭代,从改行语句开始继续执行
    yield 3

test = odd()
for item in test:  #本质还是调用next方法
    print(item)  #item 即为yield关键字返回的值
out:
	step 1
	1
	step 2
	3

其它

生成器内有一个方法send(),可以实现生成器与外界的交互。send() 和next()一样,都能让生成器继续往下走一步(下次遇到yield停),但send()能传一个值,这个值作为yield表达式整体的结果【即上一次被挂起时yield语句的返回值】
以上解释比较抽象,具体看实例:

def MyGenerator():
    value = (yield 1)
    value = (yield value)
 
gen = MyGenerator()
print gen.next()
print gen.send(2)
print gen.send(3)

输出结果:

1
2
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    print gen.send(3)
StopIteration

上面代码的运行过程如下:

1.当调用gen.next()方法时,python首先会执行MyGenerator方法的yield 1语句。由于是一个yield语句,因此方法的执行过程被挂起,而next方法返回值为yield关键字后面表达式的值,即为1。
2.当调用gen.send(2)方法时,python首先恢复MyGenerator方法上次挂起的地方,并将表达式(yield 1)的返回值定义为send方法参数的值,即为2【此时(yield 1)这个整体等于2】。接下来value=(yield 1)这一赋值语句会将value的值置为2。继续运行会遇到yield value语句,此时MyGenerator方法再次被挂起。同时,send方法的返回值为yield关键字后面表达式的值,也即value的值,为2。
3.当调用send(3)方法时,python继续恢复MyGenerator方法上次挂起的地方。同时,将表达式(yield value)的返回值定义为send方法参数的值,即为3。这样,接下来value=(yield value)这一赋值语句会将value的值置为3。继续运行,MyGenerator方法执行完毕,故而抛出StopIteration异常。

通过上面的流程,我们可以发现send()方法可以强行修改上一个yield表达式值,实现与生成器方法的交互。但是需要注意,在一个生成器对象没有执行next方法之前,由于没有yield语句被挂起,所以执行send方法会报错。

gen = MyGenerator()
print gen.send(2)

代码输出:

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    print gen.send(2)
TypeError: can't send non-None value to a just-started generator

此时可以通过send(None)解决,send(None)与next方法完全等价。

gen = MyGenerator()
print gen.send(None)

声明:关于博客内容,部分内容参考自互联网,如有侵权请联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值