Как я могу узнать, был ли только что запущен генератор?
Мне нужна функция 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
Однако это кажется отвратительным.
Ответы
Ответ 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 и итераторами