分享

Python迭代器和生成器详解(包括yield详解)

 wenxuefeng360 2022-07-16 发布于四川

一、迭代器

迭代器即用来迭代取值的工具,是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
迭代器只能往前不能后退

1. 可迭代对象(Iterable)

通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。

对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。

字符串、列表、元组、字典、集合、打开的文件都是可迭代对象

迭代器对象可以使用常规for语句进行遍历,也可以使用 next() 函数。

2. 迭代器对象(Iterator)

迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象。

执行迭代器对象 .iter() 方法得到的仍然是迭代器本身

执行迭代器 .next() 方法就会计算出迭代器中的下一个值

3. for 循环原理

有了迭代器后,便可以不依赖索引迭代取值了,使用while循环的实现方式如下:

goods=['mac','lenovo','acer','dell']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
    try:
        print(next(i))
    except StopIteration: #捕捉异常终止循环
        break

for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为:

goods=['mac','lenovo','acer','dell']
for item in goods:   
    print(item)

for 循环在工作时:

首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,

然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环,

周而复始,直到捕捉StopIteration(停止迭代),结束迭代。

4. 迭代器的优缺点

优点

1、为序列和非序列类型提供了一种统一的迭代取值方式

2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。

迭代器同一时刻在内存中只有一个值,因而可以存放无限大的数据流。

而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

缺点

1、除非取尽,否则无法获取迭代器的长度。

2、只能取下一个值,不能回到开始

迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next。

若是要再次迭代同个对象,只能重新调用iter方法创建一个新的迭代器对象。

如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

二、生成器

使用了 yield 的函数被称为生成器(generator)。

生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器

1. yield 原理

yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。

当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。

2. yield 和 return 区别

yield可以用于返回值,但不同于return。

函数一旦遇到return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。

yield可以保存函数的运行状态,挂起函数,用来返回多次值。

因此,以 yield 进行执行流控制的函数称为生成器函数,以 return 进行执行流控制的函数就是普通函数。

由于可以临时挂起函数的执行,yield 可以充当其调用者和被挂起函数间交互的桥梁。

3. yield 表达式应用

在函数内可以采用表达式形式的yield

def eater():
    print('Ready to eat.')
    while True:
        food = yield
        print('get the food: %s, and start to eat.' % food)

可以拿到函数的生成器对象持续为函数体send值,如下

>>> g=eater() # 得到生成器对象
>>> g
<generator object eater at 0x101b6e2b0>
>>> next(e) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值
Ready to eat
>>> g.send('包子')
get the food: 包子, and start to eat
>>> g.send('鸡腿')
get the food: 鸡腿, and start to eat

针对表达式形式的yield,生成器对象必须事先被初始化一次

让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值。

g.send(None)等同于next(g)。

可以编写装饰器来完成为所有表达式形式 yield 对应生成器的初始化操作,如下

def init(func):
    def wrapper(*args,**kwargs):
        g = func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def eater():
    print('Ready to eat.')
    while True:
        food = yield
        print('get the food: %s, and start to eat.' %food)

表达式形式的yield也可以用于返回多次值,即 变量名=yield 值 的形式,如下

>>> def eater():
...     print('Ready to eat')
...     food_list = []
...     while True:
...         food = yield food_list
...         food_list.append(food)
... 
>>> e=eater()
>>> next(e)
Ready to eat
[]
>>> e.send('蒸羊羔')
['蒸羊羔']
>>> e.send('蒸熊掌')
['蒸羊羔', '蒸熊掌']
>>> e.send('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']

4. 生成器的优点

  1. 精简代码

使用 yield 关键字或者生成器表达式可以很方便的生成一个迭代器对象。

为了说明这一点,我们来比较一下对于一个需求的不同实现。

该需求很简单:获取前 n 个自然数。

1、构造一个数组然后返回。当 n 很小的时候,该实现没有什么问题,但是当 n 变得很大,内存是吃不消的。

2、用生成器模式,不用 yield。构造一个对象,并实现__iter__ 和 __next__方法。但为了实现一个简单的需求却不得不构造冗长的代码。

3、使用了 yield 构造一个生成器函数

# 产生项目而不是返回列表的生成器
def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

sum_of_first_n = sum(firstn(1000000))
  1. 提高性能

这一条主要是针对内存使用上来说的。

因为迭代器不会保存所有值,而是在运行中动态的计算出数列的各个值,并将之前的数值扔掉。

这里举个实例。

假设我们有一个很大的文件(比如说 16G ) ,但是你的电脑只有 8G 内存,你如何利用 Python 对其进行处理?

答案是使用 yield 构造生成器。

def read_by_chunks(file, chunk_size=1024):
    while True:
        data = file.read(chunk_size)
        if not data:
            break
        yield data
        
f = open('your_big_file.dat')
for chunk in read_by_chunks(f):
    process_chunk(chunk)

这种通过构造生成器逐块读取的方法叫做惰性加载,也叫流式读取,是处理大文件的一种常见方式。

惰性加载也被运用在软件设计和网页设计当中,其特征为用户通过鼠标,滚动浏览页面,直到页面下方时,就会自动加载更多内容。

如果你的是文本文件,可以按行读取,那代码就更简单了:

with open('your_big_file.txt') as f:
    for line in f: 
        process_line(line)

Python文件句柄(file handle)就是一个迭代器,默认会将文件按行分割以惰性加载。 

结语

以上这些,就是Python迭代器与生成器的基础知识,希望对大家有所帮助。 如果大家有任何疑问请给我留言,我会尽快回复大家。在此也非常感谢大家对CSDN的支持! 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多