python - 使用 IPython 时生成器中出现意外的 DeprecationWarning

标签 python python-3.x ipython

我正在使用 python docs 提供的 grouper 配方的修改形式:

from itertools import chain, islice
def grouper(iterable, n):
    iterable = iter(iterable)
    while True:
        peek = next(iterable)
        yield chain((peek,), islice(iterable, n - 1))

这似乎工作正常。我可以做这样的事情:

>>> x = bytearray(range(16))
>>> [int.from_bytes(n, 'big') for n in grouper(x, 4)]
[66051, 67438087, 134810123, 202182159]

但是,当我在 IPython 中运行完全相同的代码时,我收到一个 DeprecationWarning:

In [1]: from itertools import chain, islice
   ...: def grouper(iterable, n):
   ...:      iterable = iter(iterable)
   ...:      while True:
   ...:          peek = next(iterable)
   ...:          yield chain((peek,), islice(iterable, n - 1))

In [2]: x = bytearray(range(16))

In [3]: [int.from_bytes(n, 'big') for n in grouper(x, 4)]
__main__:1: DeprecationWarning: generator 'grouper' raised StopIteration
Out[3]: [66051, 67438087, 134810123, 202182159]

警告从何而来?为什么我在常规 Python 控制台中看不到它?我该怎么做才能让警告消失?

我使用的是 Python 3.6.2 和 IPython 6.1.0

最佳答案

这是对 Python 的一项更改,正在 Python 3.5 和 Python 3.7 之间逐步进行。详细说明PEP 479 ,但我会尝试快速概述。

问题是从生成器函数中泄漏的 StopIteration 异常。看起来这似乎没什么问题,因为引发 StopIteration 是迭代器完成的信号。但它可能会导致重构生成器出现问题。下面是一个显示问题的示例:

假设您有这个生成器函数(在 3.5 之前的 Python 版本中工作正常,它开始发出警告):

def gen():
    yield 1
    yield 2
    if True:
        raise StopIteration
    yield 3
    yield 4

由于 if 的条件为真,生成器将在生成两个值后停止(不生成 34)。但是,如果您尝试重构函数的中间部分怎么办?如果将部分从 yield 2 移动到 yield 3 到辅助生成器中,您会看到一个问题:

def gen():
    yield 1
    yield from refactored_helper()
    yield 4

def refactored_helper():
    yield 2
    if True:
        raise StopIteration
    yield 3

在此版本中,3 将被跳过,但 4 仍将产生。这是因为 yield from 吃掉了辅助生成器函数中引发的 StopIteration。它假设只应该停止辅助生成器,因此外部生成器继续运行。

为了解决这个问题,Python 开发人员决定改变生成器的工作方式。从 Python 3.7 开始,从生成器函数泄漏的 StopIteration 异常将被解释器更改为 RuntimeError 异常。如果你想正常退出一个生成器,你需要使用return。此外,您现在可以从生成器函数返回一个值。该值将包含在由生成器机制引发的 StopIteration 异常中,并且 yield from 表达式将计算出返回值。

因此上面的生成器可以正确地重构为:

def gen():
    yield 1
    if yield from refactored_helper():
        return
    yield 4

def refactored_helper():
    yield 2
    if True:
        return True
    yield 3
    # like a normal function, a generator returns None if it reaches the end of the code

如果您现在想编写与 future 兼容的代码,您应该将 from __future__ import Generator_stop 放在模块的顶部。然后,您需要跟踪泄漏 StopIteration 异常的位置,并用 try except 逻辑包装它们。对于您问题中的代码:

from __future__ import generator_stop

from itertools import chain, islice

def grouper(iterable, n):
    iterable = iter(iterable)
    while True:
        try:
            peek = next(iterable)
        except StopIteration:
            return
        yield chain((peek,), islice(iterable, n - 1))

关于python - 使用 IPython 时生成器中出现意外的 DeprecationWarning,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45604899/

相关文章:

python - 从 Python 读取 FoxPro DBF 文件的最简单方法是什么?

python - 提高将 Lab 颜色列表映射到第二个 Lab 颜色列表的速度

python - 将 .txt 或 excel 文件的行读入元组

python - `format()` 和 `str()` 之间有什么区别?

python - Jupyter notebook 中的 !ls (Python 3)

python - 如何同时捕获和显示 ipython (jupyter notebook) 单元格输出?

python - 如何在事后方便地修改 matplotlib 图(持久化/序列化)?

python编码十六进制用00填充每个字符

python - 使用OpenCV退出代码-1073741819(0xC0000005)

python - Junit HTML 报告生成器