【python3-cookbook】第四章:迭代器与生成器

原文:https://python3-cookbook.readthedocs.io/zh_CN/latest/c04/p03_create_new_iteration_with_generators.html

4.1 手动遍历迭代器

为了手动的遍历可迭代对象,使用 next() 函数并在代码中捕获 StopIteration 异常。StopIteration 用来指示迭代的结尾

# 动读取一个文件中的所有行
def manual_iter():
    with open('/etc/passwd') as f:
        try:
            while True:
                line = next(f)
                print(line, end='')
        except StopIteration:
            pass

如果你手动使用上面演示的 next() 函数的话,你还可以通过返回一个指定值来标记结尾,比如 None。

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

4.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)

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

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

# 一个生产某个范围内浮点数的生成器
def frange(start, stop, increment):
    x = start
    while x < stop:
        yield x
        x += increment

yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield

def _get_number():
    b=2
    for x in [1,2,3,4,5,6]:
        yield b
        b=b+x
    return b

 

 一个函数中需要有一个 yield 语句即可将其转换为一个生成器。 跟普通函数不同的是,生成器只能用于迭代操作。

4.4 实现迭代器协议

问题

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

解决方案

目前为止,在一个对象上实现迭代最简单的方式是使用一个生成器函数。 在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 语句)返回对应元素。

4.5 反向迭代

# 反方向迭代一个序列
>>> a = [1, 2, 3, 4]
>>> for x in reversed(a):
...     print(x)

反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行。

要注意的是如果可迭代对象元素很多的话,将其预先转换为一个列表要消耗大量的内存。

 可以通过在自定义类上实现 __reversed__() 方法来实现反向迭代

 

posted @ 2022-04-29 11:04  牛奶加布丁  阅读(47)  评论(0)    收藏  举报