How can I tell whether a generator was just-started?

PythonGeneratorYield

Python Problem Overview


I'd like a function, is_just_started, which behaves like the following:

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

How can I implement this function?

I looked at the .gi_running attribute but it appears to be used for something else.

If I know the first value that needs to be sent into the generator, I can do something like this:

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

However, this seems abhorrent.

Python Solutions


Solution 1 - Python

This only works in 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'

So, the requested function is:

import inspect

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

Out of curiosity, I looked into CPython to figure out how it was determining this... Apparently it looks at generator.gi_frame.f_lasti which is the "index of last attempted instruction in bytecode". If it's -1 then it hasn't started yet.

Here's a py2 version:

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

Solution 2 - Python

Make a new generator which simply yields from your generator of interest. It sets a flag once the first value has been consumed. Afterwards, it can simply use yield from for the rest of the items.

Use the substitute generator as a drop in replacement for the generator you're interested in monitoring the "is_just_started" state.

This technique is non-intrusive, and can be used even on generators for which you have no control over the source code.

Solution 3 - Python

You may create a iterator and set the flag as the instance property to iterator class as:

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

And your value check function would be like:

def is_just_started(my_generator):
    return my_generator.is_just_started

Sample run:

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

To know the difference between iterator and generator, check Difference between Python's Generators and Iterators

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionClaudiuView Question on Stackoverflow
Solution 1 - PythonTim TisdallView Answer on Stackoverflow
Solution 2 - PythonwimView Answer on Stackoverflow
Solution 3 - PythonMoinuddin QuadriView Answer on Stackoverflow