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

目录
案例
目录
案例
迭代器与生成器(一)1.手动遍历迭代器
2.代理迭代
3.使用生成器创建新的迭代模式
4.实现迭代器协议
迭代器与生成器(三)9.排列组合的迭代
10.序列上索引值迭代
11.同时迭代多个序列
12.不同集合上元素的迭代
迭代器与生成器(二)5.反向迭代
6.带有外部状态的生成器函数
7.迭代器切片
8.跳过可迭代对象的开始部分
迭代器与生成器(四)13.创建数据处理管道
14.展开嵌套的序列
15.顺序迭代合并后的排序迭代对象
16.迭代器代替 while 无限循环

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:
            break
        print(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 = value
        self._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 = start
    while x < stop:
        yield x
        x += 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 = value
        self._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 self
        for 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 = value
        self._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_node
        self._children_iter = None
        self._child_iter = None

    def __iter__(self):
        return self

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

G皮T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值