【问题标题】:How can I tell whether a generator was just-started?如何判断发电机是否刚刚启动?
【发布时间】:2017-05-09 11:45:02
【问题描述】:

我想要一个函数,is_just_started,其行为如下:

>>> def gen(): yield 0; yield 1
>>> a = gen()
>>> is_just_started(a) 
True
>>> next(a)
0
>>> is_just_started(a) 
False
>>> next(a)
1
>>> is_just_started(a) 
False
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> is_just_started(a)
False

如何实现这个功能?

我查看了.gi_running 属性,但它似乎用于其他用途。

如果我知道需要发送到生成器的第一个值,我可以这样做:

def safe_send(gen, a):
    try:
        return gen.send(a)
    except TypeError as e:
        if "just-started" in e.args[0]:
            gen.send(None)
            return gen.send(a)
        else:
            raise

但是,这似乎很可恶。

【问题讨论】:

标签: python generator yield


【解决方案1】:

这仅适用于 Python 3.2+:

>>> def gen(): yield 0; yield 1
... 
>>> a = gen()
>>> import inspect
>>> inspect.getgeneratorstate(a)
'GEN_CREATED'
>>> next(a)
0
>>> inspect.getgeneratorstate(a)
'GEN_SUSPENDED'
>>> next(a)
1
>>> inspect.getgeneratorstate(a)
'GEN_SUSPENDED'
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> inspect.getgeneratorstate(a)
'GEN_CLOSED'

所以,请求的函数是:

import inspect

def is_just_started(gen):
    return inspect.getgeneratorstate(gen) == inspect.GEN_CREATED:

出于好奇,我研究了 CPython 以弄清楚它是如何确定这一点的……显然它查看了 generator.gi_frame.f_lasti,它是“字节码中最后一次尝试指令的索引”。如果是-1,那么它还没有开始。

这是一个 py2 版本:

def is_just_started(gen):
    return gen.gi_frame is not None and gen.gi_frame.f_lasti == -1

【讨论】:

  • 这就是我喜欢stackoverflow的原因。更好的是,它看起来像是well-defined
  • GEN_RUNNING 有什么用?文档说“如果它当前正在由解释器执行”,但我认为这是无意义的,因为 GIL?
  • @cat 措辞有点奇怪。生成器可能正在运行但被 IO 阻塞(例如this gist)。我怀疑如果生成器正在执行但被调度程序延迟,也会发生这种情况。
  • @cat:也许你是 in 生成器。它也将是在其他线程中运行的生成器的状态,无论这些线程是否被 GIL 阻塞。
  • @cat: def g(): yield inspect.getgeneratorstate(x) x = g(); next(x)
【解决方案2】:

制作一个新的生成器,它只从您感兴趣的生成器生成。 一旦第一个值被消耗,它就会设置一个标志。之后,它可以简单地使用yield from 来处理其余的项目。

使用替代生成器作为您对监视“is_just_started”状态感兴趣的生成器的替代品。

这种技术是非侵入性的,甚至可以在您无法控制其源代码的生成器上使用。

【讨论】:

  • 这种技术的优势在于它适用于所有 Python 版本——例如包括 Pypy。
【解决方案3】:

您可以创建一个迭代器并将标志设置为迭代器类的实例属性:

class gen(object):
    def __init__(self, n):
        self.n = n
        self.num, self.nums = 0, []
        self.is_just_started = True  # Your flag

    def __iter__(self):
        return self

    # Python 3 compatibility
    def __next__(self):
        return self.next()

    def next(self):
        self.is_just_started = False  # Reset flag with next
        if self.num < self.n:
            cur, self.num = self.num, self.num+1
            return cur
        else:
            raise StopIteration()

你的价值检查功能会是这样的:

def is_just_started(my_generator):
    return my_generator.is_just_started

示例运行:

>>> a = gen(2)

>>> is_just_started(a)
True

>>> next(a)
0
>>> is_just_started(a)
False

>>> next(a)
1
>>> is_just_started(a)
False

>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 19, in next
StopIteration

要了解 iteratorgenerator 之间的区别,请查看Difference between Python's Generators and Iterators

【讨论】:

  • 那不是发电机。
  • @wim:确实如此。对不起我的术语基于类的生成器,正确的词应该是iterator。更新了答案。
猜你喜欢
  • 2022-01-23
  • 2019-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-12
  • 2016-02-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多