当前位置: 首页 > news >正文

【Python Cookbook】迭代器与生成器(一)

迭代器与生成器(一)

  • 1.手动遍历迭代器
  • 2.代理迭代
  • 3.使用生成器创建新的迭代模式
  • 4.实现迭代器协议

1.手动遍历迭代器

你想遍历一个可迭代对象中的所有元素,但是却不想使用 for 循环。

为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异常。比如,下面的例子手动读取一个文件中的所有行:

def manual_iter():with open('/etc/passwd') as f:try:while True:line = next(f)print(line, end='')except StopIteration:pass

通常来讲, StopIteration 用来指示迭代的结尾。然而,如果你手动使用上面演示的 next() 函数的话,你还可以通过返回一个指定值来标记结尾,比如 None 。下面是示例:

with open('/etc/passwd') as f:while True:line = next(f, None)if line is None:breakprint(line, end='')

大多数情况下,我们会使用 for 循环语句用来遍历一个可迭代对象。但是,偶尔也需要对迭代做更加精确的控制,这时候了解底层迭代机制就显得尤为重要了。

下面的交互示例向我们演示了迭代期间所发生的基本细节:

>>> items = [1, 2, 3]
>>> # Get the iterator
>>> it = iter(items) # Invokes items.__iter__()
>>> # Run the iterator
>>> next(it) # Invokes it.__next__()
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration
>>>

2.代理迭代

你构建了一个自定义容器对象,里面包含有列表、元组或其他可迭代对象。你想直接在你的这个新容器对象上执行迭代操作。

实际上你只需要定义一个 __iter__() 方法,将迭代操作代理到容器内部的对象上去。比如:

class Node:def __init__(self, value):self._value = valueself._children = []def __repr__(self):return 'Node({!r})'.format(self._value)def add_child(self, node):self._children.append(node)def __iter__(self):return iter(self._children)# Example
if __name__ == '__main__':root = Node(0)child1 = Node(1)child2 = Node(2)root.add_child(child1)root.add_child(child2)# Outputs Node(1), Node(2)for ch in root:print(ch)
  • __repr__ 方法
    • 定义对象的字符串表示形式,方便调试和输出。
    • 例如,如果 _value3,打印该节点会显示 Node(3)
    • !r 表示使用 repr() 方法格式化 self._value,保留值的原始表示(如字符串会带引号)。
  • __iter__ 方法
    • 使 Node 对象可迭代,可以直接遍历其子节点。
    • 返回 self._children 的迭代器,这样可以对子节点进行循环操作(如 for child in node:)。

在上面代码中,__iter__() 方法只是简单的将迭代请求传递给内部的 _children 属性。

Python 的迭代器协议需要 __iter__() 方法返回一个实现了 __next__() 方法的迭代器对象。如果你只是迭代遍历其他容器的内容,你无须担心底层是怎样实现的。你所要做的只是传递迭代请求既可。

这里的 iter() 函数使用了简化的代码,iter(s) 只是简单的通过调用 s.__iter__() 方法来返回对应的迭代器对象,就跟 len(s) 会调用 s.__len__() 原理是一样的。

3.使用生成器创建新的迭代模式

你想实现一个自定义迭代模式,跟普通的内置函数比如 range()reversed() 不一样。

如果你想实现一种新的迭代模式,使用一个生成器函数来定义它。下面是一个生产某个范围内浮点数的生成器:

def frange(start, stop, increment):x = startwhile x < stop:yield xx += increment

为了使用这个函数,你可以用 for 循环迭代它或者使用其他接受一个可迭代对象的函数(比如 sum()list() 等)。示例如下:

>>> for n in frange(0, 4, 0.5):
...     print(n)
...
0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
>>> list(frange(0, 1, 0.125))
[0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]
>>>

一个函数中需要有一个 yield 语句即可将其转换为一个生成器。跟普通函数不同的是,生成器只能用于迭代操作。下面是一个实验,向你展示这样的函数底层工作机制:

>>> def countdown(n):
...     print('Starting to count from', n)
...     while n > 0:
...         yield n
...         n -= 1
...     print('Done!')
...>>> # Create the generator, notice no output appears
>>> c = countdown(3)
>>> c
<generator object countdown at 0x1006a0af0>>>> # Run to first yield and emit a value
>>> next(c)
Starting to count from 3
3>>> # Run to the next yield
>>> next(c)
2>>> # Run to next yield
>>> next(c)
1>>> # Run to next yield (iteration stops)
>>> next(c)
Done!
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration
>>>

一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。一旦生成器函数返回退出,迭代终止。我们在迭代中通常使用的 for 语句会自动处理这些细节,所以你无需担心。

4.实现迭代器协议

你想构建一个能支持迭代操作的自定义对象,并希望找到一个能实现迭代协议的简单方法。

目前为止,在一个对象上实现迭代最简单的方式是使用一个生成器函数。在第 2 小节中,使用 Node 类来表示树形数据结构。你可能想实现一个以深度优先方式遍历树形节点的生成器。

下面是代码示例:

class Node:def __init__(self, value):self._value = valueself._children = []def __repr__(self):return 'Node({!r})'.format(self._value)def add_child(self, node):self._children.append(node)def __iter__(self):return iter(self._children)def depth_first(self):yield selffor c in self:yield from c.depth_first()# Example
if __name__ == '__main__':root = Node(0)child1 = Node(1)child2 = Node(2)root.add_child(child1)root.add_child(child2)child1.add_child(Node(3))child1.add_child(Node(4))child2.add_child(Node(5))for ch in root.depth_first():print(ch)# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)

在这段代码中,depth_first() 方法简单直观。它首先返回自己本身,并迭代每一个子节点,并通过调用子节点的 depth_first() 方法(使用 yield from 语句)返回对应元素。

Python 的迭代协议要求一个 __iter__() 方法返回一个特殊的迭代器对象,这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。但是,实现这些通常会比较繁琐。下面我们演示下这种方式,如何使用一个关联迭代器类重新实现 depth_first() 方法:

class Node2:def __init__(self, value):self._value = valueself._children = []def __repr__(self):return 'Node({!r})'.format(self._value)def add_child(self, node):self._children.append(node)def __iter__(self):return iter(self._children)def depth_first(self):return DepthFirstIterator(self)class DepthFirstIterator(object):'''Depth-first traversal'''def __init__(self, start_node):self._node = start_nodeself._children_iter = Noneself._child_iter = Nonedef __iter__(self):return selfdef __next__(self):# 1. 如果是第一次访问,返回当前节点,并初始化子节点迭代器if self._children_iter is None:self._children_iter = iter(self._node)  # 获取子节点的迭代器return self._node                       # 返回当前节点# 2. 如果正在遍历某个子节点,继续返回它的下一个节点elif self._child_iter:try:nextchild = next(self._child_iter)   # 尝试获取子节点的下一个节点return nextchildexcept StopIteration:                    # 如果子节点遍历完毕self._child_iter = None              # 重置子节点迭代器return next(self)                    # 继续处理下一个子节点# 3. 否则,获取下一个子节点,并开始深度遍历else:self._child_iter = next(self._children_iter).depth_first()  # 递归调用return next(self)                                           # 继续处理
  • Node2 类表示树节点,支持添加子节点和迭代子节点。
  • DepthFirstIterator 实现了深度优先遍历:
    • 先访问当前节点。
    • 递归遍历第一个子节点,直到叶子节点。
    • 回溯并继续遍历其他子节点。
  • 这种实现方式适用于树形结构的遍历,例如文件目录、DOM 树等。

假设我们有如下树结构:

        A/ \B   C/ \   \D   E   F

深度优先遍历顺序:A → B → D → E → C → F

root = Node2('A')
b = Node2('B')
c = Node2('C')
d = Node2('D')
e = Node2('E')
f = Node2('F')root.add_child(b)
root.add_child(c)
b.add_child(d)
b.add_child(e)
c.add_child(f)# 深度优先遍历
for node in root.depth_first():print(node)  # 输出顺序: A → B → D → E → C → F

DepthFirstIterator 类和上面使用生成器的版本工作原理类似,但是它写起来很繁琐,因为迭代器必须在迭代处理过程中维护大量的状态信息。坦白来讲,没人愿意写这么晦涩的代码。将你的迭代器定义为一个生成器后一切迎刃而解。

http://www.xdnf.cn/news/4411.html

相关文章:

  • 【Qt】初识Qt(一)
  • Oracle 12.1.0.2补丁安装全流程
  • FPGA阵列
  • ZStack文档DevOps平台建设实践
  • 设计模式每日硬核训练 Day 14:组合模式(Composite Pattern)完整讲解与实战应用
  • 基于Django实现的图书分析大屏系统项目
  • Linux 常用命令总结
  • NLP高频面试题(四十六)——Transformer 架构中的位置编码及其演化详解
  • MCP和A2A是什么?
  • FreeRTOS事件标志组
  • 【Linux】第八章 监控和管理Linux进程
  • 关于Diamond机械手的运动学与动力学的推导
  • 【力扣刷题】49字母异位词分组,不用哈希,c语言实现
  • 《AI大模型应知应会100篇》第22篇:系统提示词(System Prompt)设计与优化
  • 基础知识 - 结构体
  • 首席人工智能官(Chief Artificial Intelligence Officer,CAIO)的详细解析
  • 从“链主”到“全链”:供应链数字化转型的底层逻辑
  • 智能sc一面
  • 【cocos creator 3.x】cocos creator2.x项目升级3.x项目改动点
  • 士兵乱斗(贪心)
  • 前端api(请求后端)简易template
  • Python高级爬虫之JS逆向+安卓逆向1.5节: 控制结构
  • docker harbor私有仓库登录报错
  • Ubuntu利用docker搭建Java相关环境问题记录
  • 如何有效防止服务器被攻击
  • 在激烈竞争下B端HMI设计怎样打造独特用户体验?
  • 数组理论基础
  • 从GPT到Gemini 大模型进化史
  • ADVB发送器设计
  • Matter如何终结智能家居生态割据,重构你的居住体验?