Подтвердить что ты не робот

Как я могу узнать, был ли только что запущен генератор?

Мне нужна функция 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

Однако это кажется отвратительным.

4b9b3361

Ответ 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

Ответ 2

Создайте новый генератор, который просто дает от вашего генератора, представляющего интерес. Он устанавливает флаг, когда первое значение было потреблено. Впоследствии он может просто использовать yield from для остальных элементов.

Используйте заменяющий генератор в качестве замены для генератора, который вам нужен для контроля состояния "is_just_started".

Этот метод является неинтрузивным и может использоваться даже на генераторах, для которых у вас нет контроля над исходным кодом.

Ответ 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

Чтобы узнать разницу между итератором и генератором, проверьте Разницу между генераторами Python и итераторами