2009-12-09 18 views
48

Powiedzmy mam dwie funkcje:W pythonie istnieje sposób sprawdzenia, czy funkcja jest "funkcją generatora" przed wywołaniem?

def foo(): 
    return 'foo' 

def bar(): 
    yield 'bar' 

Pierwszy z nich to normalne funkcjonowanie, a druga jest funkcją generatora. Teraz chcę napisać coś takiego:

def run(func): 
    if is_generator_function(func): 
    gen = func() 
    gen.next() 
    #... run the generator ... 
    else: 
    func() 

Co będzie prosta implementacja is_generator_function() wyglądać? Korzystając z pakietu types mogę przetestować, czy gen jest generatorem, ale chcę to zrobić przed wywołaniem func().

Rozważmy teraz następujący przypadek:

def goo(): 
    if False: 
    yield 
    else: 
    return 

inwokacji z goo() powróci generator. Zakładam, że parser Pythona wie, że funkcja goo() ma deklarację zysku, i zastanawiam się, czy możliwe jest łatwe uzyskanie tych informacji.

Dzięki!

+1

Jest to użyteczne, aby pamiętać, że jeśli funkcja zawiera 'yield' oświadczenie, a następnie' powrót 'Instrukcja wewnątrz tej funkcji nie może mieć argumentu. Musi to być po prostu 'return', który kończy generator. Dobre pytanie! –

+0

Dobra rada, 'goo()' nie powinno być poprawne, jednak tak jest, przynajmniej tutaj (Python 2.6.2). – Carlos

+4

Notatka dla obecnych czytelników: @GregHewgill komentarz powyżej nie ma już racji, teraz możesz powrócić z argumentem (który jest przekazywany na wartość attr w StopIteration) – wim

Odpowiedz

55
>>> import inspect 
>>> 
>>> def foo(): 
... return 'foo' 
... 
>>> def bar(): 
... yield 'bar' 
... 
>>> print inspect.isgeneratorfunction(foo) 
False 
>>> print inspect.isgeneratorfunction(bar) 
True 
  • nowego w wersji 2.6 Pythona
+31

Tylko komentarz z 2014 r., Dziękując za udzielenie odpowiedzi na pytanie z roku 2011 na pytanie z 2009 r. :) – wim

+2

Pomyślałem, że to rozwiązało mój problem, ale nie jest idealnie. Jeśli funkcja jest opakowanym generatorem, takim jak 'partial (generator_fn, somearg = somevalue)', to nie zostanie to wykryte. Ani lambda nie będzie używana w podobnych okolicznościach, takich jak 'lambda x: generator_fun (x, somearg = somevalue)'. Działają zgodnie z oczekiwaniami; Kod ten eksperymentował z funkcją pomocniczą, która może generować łańcuchy, ale jeśli zostanie znaleziona normalna funkcja, zawinie ją w "generator pojedynczych elementów". –

7
>>> def foo(): 
... return 'foo' 
... 
>>> def bar(): 
... yield 'bar' 
... 
>>> import dis 
>>> dis.dis(foo) 
    2   0 LOAD_CONST    1 ('foo') 
       3 RETURN_VALUE   
>>> dis.dis(bar) 
    2   0 LOAD_CONST    1 ('bar') 
       3 YIELD_VALUE   
       4 POP_TOP    
       5 LOAD_CONST    0 (None) 
       8 RETURN_VALUE   
>>> 

Jak widać, klucz różnicą, że kod bajtowy dla bar będzie zawierać co najmniej jeden YIELD_VALUE opcodu. Polecam użycie modułu dis (przekierowanie jego wyjścia do instancji StringIO i sprawdzenie jej getvalue, oczywiście), ponieważ zapewnia to pewną miarę odporności na zmiany kodu bajtowego - zmienią się dokładne wartości liczbowe kodów, ale zdemontowana wartość symboliczna pozostanie całkiem stabilny ;-).

+0

Alex, jak się czujesz, nazywając "bla" = func() "... następnie sprawdzanie, czy typ (blah) jest generatorem? a jeśli nie, to func() zostało już wywołane :-). Myślę, że właśnie tak bym najpierw zbadał, jak to zrobić :-). – Tom

+0

Zamierzał napisać to samo, ale Python übergod wszedł pierwszy. :-) – paprika

+0

OP jest bardzo jasny w tytule Q, że chce informacji ** przed wywołaniem ** - pokazując, jak to zrobić ** po ** calling nie odpowiada na pytanie z wyraźnie wyrażonymi ograniczeniami. –

14

Właściwie, zastanawiam się, jak użyteczne byłoby takie hipotetyczne is_generator_function(). Rozważmy:

def foo(): 
    return 'foo' 
def bar(): 
    yield 'bar' 
def baz(): 
    return bar() 
def quux(b): 
    if b: 
     return foo() 
    else: 
     return bar() 

Co powinien is_generator_function() powrót do baz i quux? baz() zwraca generator, ale nie jest nim sam, a quux() może zwrócić generator lub nie.

+1

@Greg, bezwzględnie - każda opcja może zwrócić iterowalną (która może w szczególności być iteratorem i super-konkretnym generatorem, lub może nie), lub coś innego (lub nic, przez podniesienie wyjątku), w zależności od na argumentach, liczbach losowych lub fazie księżyca. Funkcja generatora jest wywoływana w taki sposób, że "jeśli zwróci coś w ogóle, zamiast podnosić, zwróci obiekt generatora" (tymczasem nie wszystkie podpalacze spełniające te warunki są funkcjami gneratora). Przypadki użycia są zatem trudne do wymyślenia. –

+0

Masz rację, to rodzaj hipotetycznego pytania. Pojawiło się, gdy czytałem prezentację Davida Beazleya na temat coroutines: http://www.dabeaz.com/coroutines/ – Carlos

+10

Fałsz, prawda, fałsz, fałsz - odpowiednio. Nie chodzi o to, czy funkcja zwraca instancję generatora, ale o to, czy jest to funkcja generatora! – wim

1

I zostały wdrożone dekorator że haki na dekorowanej funkcji zwracany/przyniosły wartości. Jego podstawowa funkcja to:

import types 
def output(notifier): 
    def decorator(f): 
     def wrapped(*args, **kwargs): 
      r = f(*args, **kwargs) 
      if type(r) is types.GeneratorType: 
       for item in r: 
        # do something 
        yield item 
      else: 
       # do something 
       return r 
    return decorator 

Działa, ponieważ funkcja dekoratora jest bezwarunkowo nazywana: jest to wartość zwracana, która jest testowana.


EDIT: Zgodnie z komentarzem Roberta Lujo, skończyło się z czymś takim:

def middleman(f): 
    def return_result(r): 
     return r 
    def yield_result(r): 
     for i in r: 
      yield i 
    def decorator(*a, **kwa): 
     if inspect.isgeneratorfunction(f): 
      return yield_result(f(*a, **kwa)) 
     else: 
      return return_result(f(*a, **kwa)) 
    return decorator 
+0

Miałem podobny przypadek i dostałem błąd: SyntaxError: 'return' z argumentem wewnątrz generatora. Kiedy o tym myślę, wygląda to logicznie, ta sama funkcja nie może być normalną funkcją i funkcją generatora w tym samym czasie. Czy to naprawdę działa w twoim przypadku? –