========================== 4.6 带有外部状态的生成器函数 ========================== ---------- 问题 ---------- 你想定义一个生成器函数,但是它会调用某个你想暴露给用户使用的外部状态值。 ---------- 解决方案 ---------- 如果你想让你的生成器暴露外部状态给用户, 别忘了你可以简单的将它实现为一个类,然后把生成器函数放到 ``__iter__()`` 方法中过去。比如: .. code-block:: python from collections import deque class linehistory: def __init__(self, lines, histlen=3): self.lines = lines self.history = deque(maxlen=histlen) def __iter__(self): for lineno, line in enumerate(self.lines, 1): self.history.append((lineno, line)) yield line def clear(self): self.history.clear() 为了使用这个类,你可以将它当做是一个普通的生成器函数。 然而,由于可以创建一个实例对象,于是你可以访问内部属性值, 比如 ``history`` 属性或者是 ``clear()`` 方法。代码示例如下: .. code-block:: python with open('somefile.txt') as f: lines = linehistory(f) for line in lines: if 'python' in line: for lineno, hline in lines.history: print('{}:{}'.format(lineno, hline), end='') ---------- 讨论 ---------- 关于生成器,很容易掉进函数无所不能的陷阱。 如果生成器函数需要跟你的程序其他部分打交道的话(比如暴露属性值,允许通过方法调用来控制等等), 可能会导致你的代码异常的复杂。 如果是这种情况的话,可以考虑使用上面介绍的定义类的方式。 在 ``__iter__()`` 方法中定义你的生成器不会改变你任何的算法逻辑。 由于它是类的一部分,所以允许你定义各种属性和方法来供用户使用。 一个需要注意的小地方是,如果你在迭代操作时不使用for循环语句,那么你得先调用 ``iter()`` 函数。比如: .. code-block:: python >>> f = open('somefile.txt') >>> lines = linehistory(f) >>> next(lines) Traceback (most recent call last): File "", line 1, in TypeError: 'linehistory' object is not an iterator >>> # Call iter() first, then start iterating >>> it = iter(lines) >>> next(it) 'hello world\n' >>> next(it) 'this is a test\n' >>>