2016-12-23 40 views
49

Chciałbym funkcję, is_just_started, która zachowuje się jak następuje:Jak mogę sprawdzić, czy generator został właśnie uruchomiony?

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

Jak mogę wdrożyć tę funkcję?

Spojrzałem na atrybut .gi_running, ale wydaje się, że jest on używany do czegoś innego.

Jeśli wiem pierwszą wartość, która musi zostać wysłana do generatora, można zrobić coś takiego:

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 

to jednak wydaje się odrażające.

+3

Czy wolno modyfikować wewnątrz samego generatora? Czy wolno to udekorować? – wim

+4

Wygląda na to, że 'gi_running' wskazuje, że interpreter aktualnie działa, więc jest fałszywe między iteracjami. –

+0

Możliwy duplikat [Czy istnieje funkcja Pythona, która sprawdza, czy generator jest uruchomiony?] (Http://stackoverflow.com/questions/17684908/is-there-a-python-function-that-checks-if-a-generator-is-start) –

Odpowiedz

69

Zadziała tylko w Pythonie 3.2 lub nowszym:

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

więc żądana funkcja IS:

import inspect 

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

Z ciekawości zajrzałem do CPython aby dowiedzieć się, jak to było określenie tego. .. Najwyraźniej wygląda na generator.gi_frame.f_lasti, który jest "indeksem ostatniej próby instrukcji w kodzie bajtowym". Jeśli jest to -1, to jeszcze się nie zaczęło.

Oto wersja PY2:

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

To dlatego kocham stackoverflow. Co więcej, wygląda na to, że jest to [dobrze zdefiniowane] (https://docs.python.org/3.2/library/inspect.html#inspect.getgeneratorstate). – Claudiu

+0

Do czego służy funkcja GEN_RUNNING? Doktor mówi: "jeśli jest on obecnie wykonywany przez tłumacza", ale myślałem, że to bezsensowne z powodu GIL? – cat

+1

@cat Phrasing jest trochę dziwne. Generator może być uruchomiony, ale zablokowany przez IO (np. [To sedno] (https://gist.github.com/earwig/c6277887f872ffd1785f9a67fc629d7c)). Podejrzewam, że stanie się tak również, jeśli generator jest w trakcie wykonywania, ale odroczony przez harmonogram. –

5

może utworzyć iterator i ustawić flagę jako właściwość instancji iterator klasy jako:

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

A twoja funkcja check wartość będzie jak: metę

def is_just_started(my_generator): 
    return my_generator.is_just_started 

Sample:

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

Aby poznać różnicę między iterator i generator, sprawdź Difference between Python's Generators and Iterators

+0

To nie jest generator. – wim

+0

@wim: Rzeczywiście prawda.Przepraszam za moją generację * opartą na klasie *, poprawne słowo powinno być * iteratorem *. Zaktualizowana odpowiedź. –

25

Utwórz nowy generator, który po prostu oddaje się z twojego generatora. Ustawia flagę po pobraniu pierwszej wartości. Następnie może po prostu użyć yield from dla pozostałych elementów.

Użyj zamiennego generatora jako zamiennika dla generatora, który chcesz monitorować stan "is_just_started".

Ta technika jest nieinwazyjna i może być używana nawet na generatorach, dla których nie masz wpływu na kod źródłowy.

+3

Ta technika ma tę zaletę, że działa na wszystkich wersjach Pythona - w tym na przykład na Pypy. –