Python中的迭代器与生成器使用及说明

 更新时间:2022年12月16日 12:24:08   作者:Lareges  
这篇文章主要介绍了Python中的迭代器与生成器使用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

一、迭代器(Iterator)

1.1 可迭代对象(Iterable)

可迭代对象,可以简单理解为可遍历对象,即能够使用 for 循环遍历的对象。Python中常见的可迭代对象有:列表、元组、字符串、集合、range、字典等。

迭代器和生成器都是可迭代对象。

对于Python中的任意对象,只要它定义了可以返回一个迭代器的 __iter__ 方法,或者定义了可以支持下标索引的 __getitem__ 方法,那么它就是一个可迭代对象。

对可迭代对象使用 __iter__ 方法后,会返回一个迭代器。

如何判断一个对象是否为可迭代对象呢?请看下例。

from collections.abc import Iterable

isinstance([1, 2, 3], Iterable)  # True
isinstance((1, 2, 3), Iterable)  # True
isinstance('123', Iterable)  # True
isinstance({1, 2, 3}, Iterable)  # True
isinstance(range(3), Iterable)  # True
isinstance({'key': 'value'}, Iterable)  # True
isinstance(123, Iterable)  # False

可以看出,我们只需要使用 isinstance(object, Iterable) 即可判断给定的 object 是否为可迭代对象。

严格来讲,isinstance() 只会将有 __iter__ 方法的对象判断为 Iterable。

换言之,仅用 __getitem__ 方法实现的可迭代对象会被 isinstance() 误判为不可迭代对象。

最正确的做法是直接尝试 iter(object),如果没有报错,则说明 object 是可迭代对象。

1.2 将可迭代对象转化为迭代器

我们可以将现有的可迭代对象转化为可迭代器:

s = '12345'
myiter = iter(s)
myiter
# <str_iterator at 0x25e6f40d130>

不断调用 next 方法来依次获取迭代器的元素:

next(myiter)
# '1'
next(myiter)
# '2'
next(myiter)
# '3'
next(myiter)
# '4'
next(myiter)
# '5'
next(myiter)
# StopIteration: 

可见迭代器执行到最后时会抛出一个 StopIteration 异常。

为避免这种异常,我们完全可以用更简单的 for 循环去遍历:

for e in myiter:
    print(e)
# 1
# 2
# 3
# 4
# 5

1.3 构造迭代器

构造一个迭代器只需要在自定义的类中实现两个方法:__iter__ 和 __next__ 。

  • 迭代器是一个可以记住遍历位置的对象。
  • 迭代器对象会从第一个元素开始访问,直到所有元素都被访问为止,且只能前进不能后退。

当我们构造类时,必须要有一个名为 __init__() 的函数,该函数可以在实例化时进行一些初始化。

  • __iter__() 方法的行为类似,可以执行操作(初始化等),但必须始终返回迭代器对象本身。
  • __next__() 方法还允许你进行其他操作,并且必须返回序列中的下一项。
class MyIter:
    def __iter__(self):
        self.count = 1
        return self
    
    def __next__(self):
        x = self.count
        self.count += 1
        return x

我们创建了一个返回数字的迭代器,每次序列的数值都将 +1。

myiter = iter(MyIter())
next(myiter)
# 1
next(myiter)
# 2
next(myiter)
# 3

如果我们一直调用 next() 的方法,则序列的值将会无限递增下去。即如果我们使用 for 循环去遍历上述迭代器,循环将永远进行下去…

myiter = iter(MyIter())
for e in myiter:
    print(e)
# 循环将一直进行下去...

为了防止迭代永远进行下去,我们可以在迭代次数达到一定值时抛出 StopIteration 异常。

class MyIter:
    def __iter__(self):
        self.count = 1
        return self
    
    def __next__(self):
        if self.count <= 5:
            x = self.count
            self.count += 1
            return x
        else:
            raise StopIteration

这样再执行 for 循环就不会一直进行下去了:

myiter = iter(MyIter())
for e in myiter:
    print(e)
# 1
# 2
# 3
# 4
# 5

二、生成器(Generator)

在Python中,一边迭代(循环)一边计算的机制,称为生成器。生成器能够迭代的关键是因为它有一个 __next__ 方法。

为什么要有生成器呢?我们知道,列表中的所有数据都存储在内存中,如果有海量数据的话将会非常消耗内存。很多时候,我们只需要访问列表中前面的元素,这样一来后面的元素所占用的空间就白白浪费了。

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

有以下两种常用方法来创建生成器:

将列表解析式中的 [] 改为 ()。在自定义的函数中使用 yield 关键字。此时这个函数就不再是一个普通函数,而是一个生成器,调用该函数就是创建了一个生成器对象。

2.1 使用 () 构造生成器

比较以下两段代码:

a = [x for x in range(3)]
type(a)
# list

a = (x for x in range(3))
type(a)
# generator

我们还可以比较列表解析式和生成器的耗时:

tic = time.time()
a = sum([x for x in range(10000000)])
toc = time.time()
print(toc - tic)
# 0.9081981182098389
tic = time.time()
a = sum((x for x in range(10000000)))
toc = time.time()
print(toc - tic)
# 0.6906485557556152

我们当然可以对生成器使用 next() 方法:

next(a)
# 0
next(a)
# 1
next(a)
# 2
next(a)
# StopIteration: 

但一般我们不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代。

2.2 使用带有 yield 关键字的函数构造生成器

带有 yield 的函数不再是一个普通函数,而是一个生成器。

yield 相当于return一个值,并且记住这个返回的位置,下次迭代时,代码从 yield 的下一条语句开始执行。

我们可以通过下面的例子先来理解一下:

def num():
    print('开始执行')
    for i in range(5):
        yield i
        print('继续执行')


mygen = num()
type(mygen)
# generator

由此,我们成功创建了一个生成器对象。接下来调用 next 方法观察这个生成器是如何工作的:

next(mygen)
# 开始执行
# 0
next(mygen)
# 继续执行
# 1
next(mygen)
# 继续执行
# 2
next(mygen)
# 继续执行
# 3
next(mygen)
# 继续执行
# 4
next(mygen)
# StopIteration:

当然我们也可以使用 for 循环来遍历这个生成器:

for step in mygen:
    print(step)
# 开始执行
# 0
# 继续执行
# 1
# 继续执行
# 2
# 继续执行
# 3
# 继续执行
# 4
# 继续执行

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • python使用 __init__初始化操作简单示例

    python使用 __init__初始化操作简单示例

    这篇文章主要介绍了python使用 __init__初始化操作,结合实例形式分析了Python面向对象程序设计中使用__init__进行初始化操作相关技巧与注意事项,需要的朋友可以参考下
    2019-09-09
  • Python线性表种的单链表详解

    Python线性表种的单链表详解

    这篇文章主要介绍了Python线性表种的单链表详解,线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列。线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直接后继
    2022-08-08
  • 详解如何使用numpy提高Python数据分析效率

    详解如何使用numpy提高Python数据分析效率

    NumPy是Python语言的一个第三方库,其支持大量高维度数组与矩阵运算。本文主要为大家介绍了如何使用numpy提高python数据分析效率,需要的可以参考一下
    2023-04-04
  • Python matplotlib绘制实时数据动画

    Python matplotlib绘制实时数据动画

    Matplotlib作为Python的2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。本文将利用Matplotlib库绘制实时数据动画,感兴趣的可以了解一下
    2022-03-03
  • 使用python计算方差方式——pandas.series.std()

    使用python计算方差方式——pandas.series.std()

    这篇文章主要介绍了使用python计算方差方式——pandas.series.std(),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • python神经网络编程之手写数字识别

    python神经网络编程之手写数字识别

    这篇文章主要介绍了python神经网络编程之手写数字识别,文中有非常详细的代码示例,对正在学习python神经网络编程的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-05-05
  • Python编程使用NLTK进行自然语言处理详解

    Python编程使用NLTK进行自然语言处理详解

    这篇文章主要介绍了Python编程使用NLTK进行自然语言处理详解,涉及了nltk和开发环境的简单介绍,以及SentencesSegment,SentencesSegment等内容,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Python使用minidom读写xml的方法

    Python使用minidom读写xml的方法

    这篇文章主要介绍了Python使用minidom读写xml的方法,实例分析了使用minidom模块操作XML文件的相关技巧,需要的朋友可以参考下
    2015-06-06
  • python pymysql peewee关于时区问题分析

    python pymysql peewee关于时区问题分析

    这篇文章主要为大家介绍了python pymysql peewee关于时区问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-06-06
  • python通过百度地图API获取某地址的经纬度详解

    python通过百度地图API获取某地址的经纬度详解

    这篇文章主要给大家介绍了关于python通过百度地图API获取某地址的经纬度的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-01-01

最新评论