【问题标题】:How to pick just one item from a generator?如何从生成器中只选择一项?
【发布时间】:2011-06-12 02:12:27
【问题描述】:

我有一个生成器函数,如下所示:

def myfunct():
  ...
  yield result

调用这个函数的常用方法是:

for r in myfunct():
  dostuff(r)

我的问题是,有没有一种方法可以随时从生成器中获取一个元素? 例如,我想做这样的事情:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...

【问题讨论】:

    标签: python iterator generator python-2.x


    【解决方案1】:

    使用创建生成器

    g = myfunct()
    

    每当您想要一件物品时,请使用

    next(g)
    

    (或在 Python 2.5 或更低版本中为 g.next())。

    如果生成器退出,它将引发StopIteration。如有必要,您可以捕获此异常,或使用default 参数到next()

    next(g, default_value)
    

    【讨论】:

    • 注意,只有在提供 g 中的最后一项后尝试使用 g.next() 时,它才会引发 StopIteration。
    • next(gen, default) 也可用于避免StopIteration 异常。例如,next(g, None) 用于字符串生成器,在迭代完成后将产生一个字符串或 None。
    • 在 Python 3000 中,next() 是 __next__()
    • @JonathanBaldwin:您的评论有些误导。在 Python 3 中,您将使用我的答案中给出的第二种语法 next(g)。这将在内部调用g.__next__(),但您不必担心这一点,就像您通常不关心len(a) 内部调用a.__len__() 一样。
    • 我应该更清楚。 g.next() 在 py3k 中是 g.__next__()。内置的 next(iterator) 自 Python 2.6 以来就已经存在,并且应该在所有新的 Python 代码中使用,如果您需要支持 py
    【解决方案2】:

    如果只选择生成器的一个元素,请在for 语句中使用break,或list(itertools.islice(gen, 1))

    根据您的示例(字面意思),您可以执行以下操作:

    while True:
      ...
      if something:
          for my_element in myfunct():
              dostuff(my_element)
              break
          else:
              do_generator_empty()
    

    如果你想“只从 [once generated] generator只要我喜欢的时候得到一个元素”(我想50%这就是初衷,也是最常见的意图)然后:

    gen = myfunct()
    while True:
      ...
      if something:
          for my_element in gen:
              dostuff(my_element)
              break
          else:
              do_generator_empty()
    

    这样可以避免显式使用generator.next(),并且输入结束处理不需要(神秘的)StopIteration 异常处理或额外的默认值比较。

    else:for 语句部分仅在您想要在生成器结束的情况下执行特殊操作时才需要。

    注意next()/.next()

    在 Python3 中,.next() 方法被重命名为 .__next__() 有充分的理由:它被认为是低级的 (PEP 3114)。在 Python 2.6 之前,内置函数 next() 不存在。甚至讨论过将next() 移至operator 模块(这本来是明智的),因为它很少需要并且内置名称的膨胀存在问题。

    在没有默认值的情况下使用next() 仍然是非常低级的做法 - 在正常的应用程序代码中公开地抛出神秘的StopIteration 就像一个晴天霹雳。并且使用带有默认哨兵的next() - 最好应该是直接在builtins 中的next() 的唯一选择 - 是有限的,并且通常会给出奇怪的非pythonic逻辑/可读性的理由。

    底线:使用 next() 应该是非常少见的——比如使用 operator 模块的函数。使用 for x in iteratorislicelist(iterator) 和其他无缝接受迭代器的函数是在应用程序级别使用迭代器的自然方式——而且总是可能的。 next() 是低级的,一个额外的概念,不明显 - 正如这个线程的问题所示。虽然例如在for 中使用break 是常规的。

    【讨论】:

    • 这对于获取列表结果的第一个元素来说工作量太大了。通常我不需要它懒惰,但在 py3 中没有选择。有没有类似于mySeq.head 的东西?
    【解决方案3】:

    Generator 是一个产生迭代器的函数。因此,一旦有了迭代器实例,就可以使用next() 从迭代器中获取下一项。 例如,使用 next() 函数获取第一项,稍后使用for in 处理剩余的项:

    # create new instance of iterator by calling a generator function
    items = generator_function()
    
    # fetch and print first item
    first = next(items)
    print('first item:', first)
    
    # process remaining items:
    for item in items:
        print('next item:', item)
    

    【讨论】:

      【解决方案4】:

      您可以使用解构选择特定项目,例如:

      >>> first, *middle, last = range(10)
      >>> first
      0
      >>> middle
      [1, 2, 3, 4, 5, 6, 7, 8]
      >>> last
      9
      

      请注意,这会消耗您的生成器,因此虽然可读性很高,但它的效率不如next() 之类的东西,并且对无限生成器来说是毁灭性的:

      >>> first, *rest = itertools.count()
      ???
      

      【讨论】:

        【解决方案5】:

        我不相信有一种方便的方法可以从生成器中检索任意值。生成器将提供一个 next() 方法来遍历自己,但不会立即生成完整的序列以节省内存。这就是生成器和列表之间的功能区别。

        【讨论】:

          【解决方案6】:
          generator = myfunct()
          while True:
             my_element = generator.next()
          

          确保捕捉到最后一个元素被取出后抛出的异常

          【讨论】:

          • 不适用于 Python 3,请参阅优秀的 answer by kxr
          • 对于 Python 3,只需将“generator.next()”替换为“next(generator)”即可。
          【解决方案7】:

          对于那些浏览这些答案以获取 Python3 的完整工作示例的人......好吧,你去吧:

          def numgen():
              x = 1000
              while True:
                  x += 1
                  yield x
          
          nums = numgen() # because it must be the _same_ generator
          
          for n in range(3):
              numnext = next(nums)
              print(numnext)
          

          这个输出:

          1001
          1002
          1003
          

          【讨论】:

            【解决方案8】:

            我相信唯一的方法是从迭代器中获取一个列表,然后从该列表中获取您想要的元素。

            l = list(myfunct())
            l[4]
            

            【讨论】:

            • Sven 的回答可能更好,但我将把它留在这里,以防它更符合您的需求。
            • 在执行此操作之前,请确保您有一个有限生成器。
            • 对不起,这个迭代器的长度很复杂,而问题显然是O(1)。
            • 浪费了太多的内存和进程从生成器中吸取!此外,正如@Seth 之前提到的,生成器不能保证何时停止生成。
            • 这显然不是唯一的方法(或者如果myfunct()生成大量值的最佳方法),因为您可以使用内置函数next获取下一个生成的值。
            猜你喜欢
            • 1970-01-01
            • 2021-06-26
            • 1970-01-01
            • 2023-04-10
            • 1970-01-01
            • 2020-11-24
            • 1970-01-01
            • 2018-10-25
            相关资源
            最近更新 更多