2016-12-23 4 views
49

Я хотел бы функцию, 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 

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

+3

Это позволило изменить внутри самого генератора? Разрешено ли его украшать? – wim

+4

похоже, что 'gi_running' указывает, что интерпретатор на самом деле в настоящий момент запускает код, поэтому он имеет значение false между итерациями. –

+0

Возможный дубликат. [Есть ли функция Python, которая проверяет, запущен ли генератор?] (Http://stackoverflow.com/questions/17684908/is-there-a-python-function-that-checks-if-a-generator-is-started) –

ответ

69

Это работает только в 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 
+12

Вот почему я люблю stackoverflow. Еще лучше, похоже, что это [четко определено] (https://docs.python.org/3.2/library/inspect.html#inspect.getgeneratorstate). – Claudiu

+0

Что такое 'GEN_RUNNING'? Док говорит, «если он в настоящее время выполняется переводчиком», но я думал, что это бессмысленно из-за GIL? – cat

+1

@cat Фразинг немного странный. Генератор может работать, но блокируется IO (например, [this gist] (https://gist.github.com/earwig/c6277887f872ffd1785f9a67fc629d7c)). Я подозреваю, что это произойдет, если генератор находится в середине выполнения, но отложен планировщиком. –

5

Вы можете создать итератор и установить флаг в качестве свойства экземпляра, чтобы итератор класса как:

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 

Чтобы узнать разницу между итератором и генератор, проверка Difference between Python's Generators and Iterators

+0

Это не генератор. – wim

+0

@wim: Действительно правда.Извините за мою терминологию * генератор на основе классов *, правильным словом должно быть * iterator *. Обновленный ответ. –

25

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

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

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

+3

Этот метод имеет то преимущество, что он будет работать на всех версиях Python, включая, например, Pypy. –

 Смежные вопросы

  • Нет связанных вопросов^_^