首页 > 其他分享 >迭代器和生成器

迭代器和生成器

时间:2024-12-25 18:42:04浏览次数:3  
标签:__ node 迭代 self 生成器 iter next

可迭代对象(iterable): 一般像list, tuple, dictionary这种,内部需要实现__iter__方法,该方法用于创建一个迭代器。
迭代器(iterator): 由可迭代对象创建,在for循环刚开始时自动创建,也可以通过iter(iterable)内置函数来创建。 其内部需要实现__next__方法。

可以把迭代器当成是对可迭代对象的流式处理,循环时,就像代表着可迭代对象中的每一个元素一样。
实际内部运行机制: 循环开始时,内部会调用可迭代对象的__iter__方法来创建一个迭代器,随后,迭代器通过每次执行一下其内部的__next__方法,来返回一个值,这个__next__方法可由我们自己来实现,当然我们的目的就是让其返回可迭代对象中的值。 同时我们要在方法内部实现停止循环的条件,即抛出StopIteration异常,这就标志着已经迭代完了所有元素。

先看下python对可迭代对象和迭代器的判断标准:
python内部将实现了__iter__方法的对象叫做可迭代对象。 将同时实现了__iter____next__两个方法的对象称为迭代器。

from collections.abc import Iterable, Iterator

class Foo:
    def __iter__(self):
        pass

print(issubclass(Foo, Iterable))
print(issubclass(Foo, Iterator))

print('-' * 25, '分隔线', '-' * 25)

class Bar:
    def __iter__(self):
        pass

    def __next__(self):
        pass

print(issubclass(Bar, Iterable))
print(issubclass(Bar, Iterator))

输出结果:

True
False
------------------------- 分隔线 -------------------------
True
True

其实我们的迭代器如果不实现__iter__方法,也能进行迭代。 但是它就不能像可迭代对象一样放在for ... in 的后边。

下面使用链表结构模拟可迭代对象和迭代器。 这里的迭代器我只实现了__next__方法,没有实现__iter__方法。

class Node:
    def __init__(self, value):
        self.value = value
        self.next_node = None

    def __iter__(self):
        return NodeIterator(self)

class NodeIterator:
    def __init__(self, node: Node):
        self.current_node = node

    def __next__(self):
        node, self.current_node = self.current_node, self.current_node.next_node
        return node


node1 = Node('first')
node2 = Node('second')
node3 = Node('third')

node1.next_node = node2
node2.next_node = node3

# 不使用for loop循环
it = iter(node1)  # 使用iter函数作用在可迭代对象上,创建一个迭代器
print(it.current_node.value)

print('-' * 25, '分隔线', '-' * 25)

first_node = next(it)
print(first_node.value)
second_node = next(it)
print(second_node.value)
third_node = next(it)
print(third_node.value)

# forth_node = next(it)  # 继续使用next操作将抛出StopIteration异常

输出结果:

first
------------------------- 分隔线 -------------------------
first
second
third

可以看到,我们使用iter函数得到了一个迭代器,然后在使用next函数作用于这个迭代器,每次都返回一个值。
下面我们使用for循环:

# 下面使用循环操作
print('-' * 25, '分隔线', '-' * 25)
for item in node1:
    print(item.value)

输出结果:

------------------------- 分隔线 -------------------------
first
second
third
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\test.py", line 42, in <module>
    for item in node1:
  File "F:\RolandWork\PythonProjects\studyPython\test.py", line 14, in __next__
    node, self.current_node = self.current_node, self.current_node.next_node
                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'next_node'

可以看到,for循环打印出了这三个节点的值,但最终报错了。原因是迭代器已经指向最后一个节点之后,我们的next函数再执行时,它已经是None了,就没有next_node属性了。 因此这时候,我们应该手动抛出StopIteration异常,这样for循环就能正常停止了。 我们只需要像下面这样修改一个迭代器的__next__方法。

class NodeIterator:
    def __init__(self, node: Node):
        self.current_node = node

    def __next__(self):
        if self.current_node is None:
            raise StopIteration
        node, self.current_node = self.current_node, self.current_node.next_node
        return node

再次运行,for循环就不报错,而是正常结束循环了。

看起来我们的迭代器工作正常。 但是为什么官方会要求迭代器自身也要实现__iter__方法呢?(即让迭代器也是一个可迭代对象)
我们来看下面这种情况:

it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)

print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
    print(item.value)

输出结果:

first
------------------------- 分隔线 -------------------------
Traceback (most recent call last):
  File "F:\RolandWork\PythonProjects\studyPython\test.py", line 34, in <module>
    for item in it:
TypeError: 'NodeIterator' object is not iterable

可以看到,运行报错了。 看样子我们不能直接把迭代器放在for循环in的后边。 这是因为,in后面要放一个可迭代对象,因为像我们一开始说的,for循环内部开始时,会先对我们in后面的对象执行iter()方法,来返回一个迭代器。而我们的迭代器目前没有实现__iter__方法,所以它不算是可迭代对象。
如果我们想这么用,就在迭代器里实现__iter__方法,而实现也很简单,就直接返回它自己就可以了。这样,迭代器自己返回了自己,当前指向哪个节点它当然也记得住。
在迭代器NodeIterator类中添加如下方法就可以了。

def __iter__(self):  # 迭代器也实现__iter__方法,以便可以用在for in 后边
   return self 

修改后再次运行,结果如下:

first
------------------------- 分隔线 -------------------------
second
third

现在还有一个问题,就是当我们运行完一遍for循环后,再对这个迭代器运行一次for循环,我们发现就没有再打印任何节点了。 这与我们认为的for循环每次都会从头开始打印一遍的想法不一样。

it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)

print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
    print(item.value)

print('-' * 25, '分隔线', '-' * 25)
for item in it:
    print(item.value)

输出结果:

first
------------------------- 分隔线 -------------------------
second
third
------------------------- 分隔线 -------------------------

而如果我们把第二次for循环的in后面的it换成node1,或者iter(node1),就可以正常打印第二次循环的内容。

it = iter(node1)
node = next(it) # 这行代码运行后,迭代器已经指向了第二个节点
print(node.value)

print('-' * 25, '分隔线', '-' * 25)
# 这时我们想接下来使用for循环从当前指向的节点继续迭代下去
for item in it:
    print(item.value)

print('-' * 25, '分隔线', '-' * 25)
for item in node1:  # 或者换成iter(node1)
    print(item.value)

输出结果:

first
------------------------- 分隔线 -------------------------
second
third
------------------------- 分隔线 -------------------------
first
second
third

这是因为,换成node1或iter(node1)时,迭代器对象被使用iter()方法在循环一开始重新获取到了。 而之前我们的迭代器对象(it)已经指向最后一个节点了,再对它循环,就相当于再对它执行next()函数,内部就会立刻遇到StopIteration异常,从而结束循环。 为了让迭代器对象可以重复使用,我们可以在每次其指向最后一个节点,并在遇到StopIteration异常之前,重新让它指向第一个节点,为再次使用for循环做准备。
于是可以像下面这样修改代码: 在抛出异常前,加一句 self.current_node = node1

class NodeIterator:
    def __init__(self, node: Node):
        self.current_node = node

    def __next__(self):
        if self.current_node is None:
            self.current_node = node1  # 每次迭代到最后一个节点时,再将current_node属性指回第一个节点,这样下次循环迭代器,就可以又重头开始。
            raise StopIteration
        node, self.current_node = self.current_node, self.current_node.next_node
        return node

    def __iter__(self):  # 迭代器也实现__iter__方法,以便可以用在for in 后边
        return self

标签:__,node,迭代,self,生成器,iter,next
From: https://www.cnblogs.com/rolandhe/p/18407670

相关文章

  • 面试场景题系列:分布式系统中的唯一ID生成器
    1.场景需求需求界定•ID必须是唯一的。•ID只包含数字。•ID长为64位。•ID按日期排序。•可以每秒生成超过10,000个唯一ID。2.高层级的设计在分布式系统中,有多个方法可以用来生成唯一ID。我们考虑的方法有:•多主复制(Multi-masterReplication)。•通用唯一标识符......
  • Electron+Vue3实现源代码生成器
    Electron+Vue3实现源代码生成器开源项目地址功能实现存在问题开源项目地址gitee链接:传送门github链接:传送门功能实现根据用户选择的文件夹,生成指定后缀名的TXT源代码文档,效果如下:初始页面选择文件夹,添加后缀名后生成成功后生成效果存在问题因为是第......
  • 【es6复习笔记】生成器(11)
    什么是生成器函数生成器函数是一种特殊的函数,它可以在执行过程中暂停并保存当前状态,然后在需要时恢复执行。生成器函数通过yield关键字来实现暂停和恢复执行的功能。生成器函数的基本用法定义生成器函数:使用function*关键字来定义生成器函数。使用yield关键字:在生......
  • AI音效生成器:将文字描述转化为高质量音效的创新技术
    在当前的多媒体内容创作中,高质量音效的获取与应用对于提升作品的吸引力与沉浸感至关重要。为此,一款基于先进AI技术的在线音效生成器,该工具能够将文字描述精准地转换成多种类型的高质量音效,涵盖自然声音、乐器声等多种范畴。技术特点即时生成能力:这款AI音效生成器具备即时......
  • 学习高校课程-软件设计模式-迭代者模式和中介者模式
    Iterator:ProblemCollectionsareoneofthemostuseddatatypesinprogrammingThereshouldbeawaytogothrougheachelementofthecollectionMoretraversalalgorithmstothecollectionblursitsprimaryresponsibilitySomealgorithmsmightbetailo......
  • 构建 LLM 商业应用:迭代升级与关键要素全解析
    摘要:在当今数字化时代,大语言模型(LLM)正深刻改变着商业格局。从智能客服到精准营销,从内容生成到风险预测,LLM的商业应用潜力巨大。然而,要构建高效、可靠的LLM商业应用并非一蹴而就,需要经历系统的迭代步骤,同时充分认识到精准语料的重要性以及掌握有效的语料加工流程。本文将......
  • C++STL--迭代器
    本文章粗浅的介绍了什么是迭代器,帮助小伙伴们理解!!!文章目录一、迭代器是什么?二、使用迭代器三、begin和end的运算四、结合解引用和成员访问五、迭代器的运算六、迭代编程思想总结一、迭代器是什么?迭代器(iterator)是一种设计模式,在编程中广泛用于遍历(迭代)集合(......
  • java-代码生成器
    代码生成器主要作用就是为我们减少开发的时间,提高开发效率。我们先看效果图(修改后的):刚开始生成的还有实体类不过因为我其他地方已经有了所有删除了一.导入相关依赖pom.xml<!--mybatis-plus代码生成器--><dependency><groupId>com.baomidou</groupId><artifac......
  • Solana靓号地址生成器
    现实生活中,有人追求手机号“111111”,而在区块链中,大家也追求“靓号地址”。靓号地址(VanityAddress)指的是包含特定字符或字符序列的加密货币地址,这些地址通常具有某种特殊的意义或美观性。如何生成自己的靓号地址呢?solana很可爱,已经为我们准备好了工具。安装SolanaClisola......
  • 两两交换链表中的节点(迭代)
    给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即只能进行节点交换)。 示例1:输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]输出:[1] /***Definitionforsing......