【问题标题】:exhausted iterators - what to do about them?用尽的迭代器——如何处理它们?
【发布时间】:2011-04-25 19:12:55
【问题描述】:

(在 Python 3.1 中) (与another question I asked 有点相关,但这个问题是关于迭代器被耗尽的问题。)

# trying to see the ratio of the max and min element in a container c
filtered = filter(lambda x : x is not None and x != 0, c)
ratio = max(filtered) / min(filtered)

我花了半个小时才意识到问题出在哪里(过滤器返回的迭代器在它到达第二个函数调用时已经耗尽)。如何以最 Pythonic / 规范的方式重写它?

另外,除了获得更多经验之外,我还能做些什么来避免此类错误? (坦率地说,我不喜欢这种语言特性,因为这些类型的错误很容易制造,很难捕捉。)

【问题讨论】:

  • 当您知道自己在做什么时,不难发现。使用 python2 尝试以下代码:python -m timeit "r = xrange(1000000000)"python -m timeit "r = range(1000000000)"。正如您所见,迭代器毕竟不是必需的,在 python3 中 range() 的行为确实类似于 xrange()

标签: python filter iterator python-3.x


【解决方案1】:

您只需调用tuple(iterator) 即可将迭代器转换为元组

但是我会将该过滤器重写为列表理解,看起来像这样

# original
filtered = filter(lambda x : x is not None and x != 0, c)

# list comp
filtered = [x for x in c if x is not None and x != 0]

【讨论】:

  • +1 是的——这正是我自己的想法。检查filtered 是否为空也变得很简单:len(filtered) != 0
  • 您不需要检查列表的长度来查看它是否为空 - 如果它为空,它将直接评估为 False,否则为 True。
【解决方案2】:

实际上,您的代码引发了一个可以防止此问题的异常!所以我猜问题是你掩盖了异常?

>>> min([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence
>>> min(x for x in ())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: min() arg is an empty sequence

无论如何,你也可以编写一个新函数来同时给你最小值和最大值:

def minmax( seq ):
    " returns the `(min, max)` of sequence `seq`"
    it = iter(seq)
    try:
        min = max = next(it)
    except StopIteration:
        raise ValueError('arg is an empty sequence')
    for item in it:
        if item < min:
            min = item
        elif item > max:
            max = item
    return min, max

【讨论】:

  • 给定一个迭代器,第二个最佳解决方案是将内容存储在一个列表中,然后根据需要多次读取该列表。如果你能做到的话,这个解决方案,调整你的算法以在迭代器的单次扫描中完成它需要的所有事情会更好。
  • 这个错误是我正在捕捉 ValueError 认为这是由于迭代器一开始是空的;不是因为我自己的代码耗尽了迭代器!
【解决方案3】:

实体filtered本质上是一个有状态的对象。当然,现在很明显,在其上运行 maxmin 会改变该状态。为了避免绊倒,我想绝对清楚(对我自己来说,真的)我正在构建一些东西,而不仅仅是改造一些东西:

添加一个额外的步骤真的很有帮助:

def filtered(container):
    return filter(lambda x : x is not None and x != 0, container)

ratio = max(filtered(c)) / min(filtered(c))

您是否将filtered(...) 放在某个函数中(也许其他任何东西都不需要)或将其定义为模块级函数取决于您,但在这种情况下,我建议如果filtered ( iterator) 仅在函数中需要,将其保留在那里,直到您在其他地方需要它。

您可以做的另一件事是从中构造一个list,它将评估迭代器:

filtered_iter = filter(lambda x : x is not None and x != 0, container)
filtered = list(filtered_iter)

ratio = max(filtered) / min(filtered)

(当然,你可以直接说filtered = list(filter(...))。)

【讨论】:

  • +1 :我会使用这种技术,尽管我仍然不喜欢整个“迭代器是可迭代的”想法......感觉就像一个等待发生的错误。
  • @max - 你总是可以在一个迭代上去list(...) 强制它被评估。我会在我的答案中添加一些内容。
  • @max - 更一般地说...除了迭代之外,迭代器还能做什么?将整个内容存储在内存中可能是不切实际或不可能的(例如,不确定的素数生成器),并且只有在您以某种方式存储迭代器结果时才可以在所有情况下进行倒带。阅读您正在使用的函数的文档,并在需要时转换为列表。或者我错过了你的意思?
【解决方案4】:

itertools.tee 函数可以在这里提供帮助:

import itertools

f1, f2 = itertools.tee(filtered, 2)
ratio = max(f1) / min(f2)

【讨论】:

  • +1 - 直到现在才听说过 tee... 整洁,虽然使用起来很棘手(必须确保我不会在调用之后重复使用过滤器,而且它的性能不会更差 -比建立列表更明智)。
  • tee 将在内部有效地制作过滤列表的完整副本,因此它几乎等同于filtered_list = list(filtered_iter)。并且您对技巧的担忧不是这个特定解决方案的问题,而是使用迭代器的固有问题 - 每当您有一个迭代器,您必须确保不要尝试使用它两次。 @THC4k 发布的答案更多的是您应该使用的 - 没有额外的迭代数据副本,也无需尝试“tee”迭代器等。
  • 如果有一种简单的方法可以将 minmax 调用交错,那么 tee 就不必在内部存储缓冲区了。
猜你喜欢
  • 2011-06-03
  • 1970-01-01
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
相关资源
最近更新 更多