【问题标题】:Unexpected DeprecationWarning in generator when using IPython使用 IPython 时生成器中出现意外的弃用警告
【发布时间】:2017-08-10 13:56:29
【问题描述】:

我正在使用 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.x ipython


    【解决方案1】:

    这是对 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。此外,您现在可以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
    

    如果您现在想编写未来兼容的代码,您应该将from __future__ import generator_stop 放在模块的顶部。然后你需要追踪你泄漏StopIteration异常的地方,并用tryexcept逻辑包装它们。对于您问题中的代码:

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

    【讨论】:

    • 这是我一直在寻找的答案。谢谢。
    • 这种情况下导入的目的是什么?它似乎没有添加任何 3.6 中尚不存在的功能。
    • 我问是因为我尝试了你的版本,而且它似乎在没有导入的情况下工作得很好。如果你想知道,顺便说一句,stackoverflow.com/a/23926929/2988730
    • 你说得对,在最后一个版本的代码中,__future__ 导入不是必需的。但是,它会破坏早期版本(他们会提出RuntimeErrors)。这个想法是添加未来的导入,查看现有代码中的哪些中断,然后将其修复为向前兼容。
    • 清晰且乐于助人。谢谢!
    【解决方案2】:

    抛出弃用警告是因为 Python 语言即将发生非向后兼容的更改,该更改将从 3.7 版开始生效,并记录在 PEP-479 中。

    最重要的部分:

    摘要

    此 PEP 提议对生成器进行更改:当 StopIteration 被提出时 在生成器内部,它被替换为 RuntimeError 。 (更多的 确切地说,这发生在异常即将冒出时 生成器的堆栈帧。)因为更改是向后的 不兼容,该功能最初是使用__future__ 引入的 声明。

    过渡计划

    Python 3.5:在__future__ 下启用新语义 进口;如果StopIteration 冒出 生成器不在__future__ 导入下。 Python 3.6:非静默弃用警告。 Python 3.7:随处启用新语义。

    应该更新您链接的文档。

    【讨论】:

      【解决方案3】:

      当一个模块将来被弃用时会抛出弃用警告。现在,你可以使用它。只需将以下内容添加到您的代码中,就不会看到这些警告:

      import warnings
      warnings.simplefilter(action='ignore', category=FutureWarning)
      warnings.simplefilter(action='ignore', category=DeprecationWarning)
      

      【讨论】:

      • 问题是它为什么会发生,而不是它是什么。
      • “我该怎么做才能让警告消失?”
      • 如果您不知道究竟是什么导致了警告,这是一个不好的建议。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-15
      • 2017-03-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多