2009-12-09 15 views
48

2つの関数があるとしましょう:Pythonでは、関数を呼び出す前に関数が "ジェネレータ関数"であるかどうかをチェックする方法はありますか?

def foo(): 
    return 'foo' 

def bar(): 
    yield 'bar' 

最初の関数は通常の関数であり、2つ目はジェネレータ関数です。今私はこのようなものを書いておきたいと思います:

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

is_generator_function()の簡単な実装はどうなりますか? typesパッケージを使用すると、genがジェネレータであるかどうかをテストできますが、func()を呼び出す前に実行したいと考えています。

次のような場合を考えてみましょう。

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

goo()を呼び出すとジェネレータが返されます。私は、pythonパーサーがgoo()関数がyield文を持っていることを知っていると推測し、その情報を簡単に取得できるかどうか疑問に思う。

ありがとう!

+1

それは注意することは便利です、その関数は 'yield'文が含まれている場合は、'リターン'関数内の文は引数を持つことができません。ジェネレータを終了させるだけの 'return'でなければなりません。良い質問! –

+0

良い点、 'goo()'は有効ではありませんが、ここでは少なくとも(Python 2.6.2)です。 – Carlos

+4

現在の読者への注:@GregHewgillのコメントはもはや正しいものではなく、今度は(StopIterationの値attrに渡される)引き数を返すことができます – wim

答えて

55
>>> import inspect 
>>> 
>>> def foo(): 
... return 'foo' 
... 
>>> def bar(): 
... yield 'bar' 
... 
>>> print inspect.isgeneratorfunction(foo) 
False 
>>> print inspect.isgeneratorfunction(bar) 
True 
  • 私が飾ら機能返される/得られた値にフックデコレータを実装しましたPythonのバージョン2.6
+31

2009年の質問に対して2011年の回答を提供してくれてありがとう、ちょうど2014年のコメント:) – wim

+2

これは私の問題を解決したと思ったが、それは完全ではない。関数が 'partial(generator_fn、somearg = somevalue)'のようなラップされたジェネレータである場合、これは検出されません。ラムダは 'lambda:generator_fun(x、somearg = somevalue)'のような同様の状況で使われることはありません。これらは実際には期待通りに動作します。コードはジェネレータを連鎖できるヘルパー関数を使って実験していましたが、通常の関数が見つかった場合は、それを「単一項目ジェネレータ」にラップします。 –

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

ご覧のとおり、重要な違いは、barのためのバイトコードは、少なくとも一つのYIELD_VALUEオペコードを含むことです。 disモジュールを使用することをお勧めします(出力をStringIOインスタンスにリダイレクトし、もちろんgetvalueをチェックすることをお勧めします)。これは、バイトコードの変更に対する堅牢性を提供するためです。オペコードの正確な数値は変更されますが、かなり安定しています;-)。

+0

アレックス、 = func() "...次に型(blah)がジェネレータであるかどうかをチェックしますか?そうでなければ、func()は既に呼び出されていました:-)。私はこれがどうやって最初にどうやって調査するのかということでした:-)。 – Tom

+0

同じことを書こうとしましたが、Pythonübergodが最初に登場しました。 :-) – paprika

+0

QのタイトルでOPが非常に明確であるため、**を呼び出す前に情報を求めています** - それを得る方法を示しています**呼び出し後に明確に表現された制約で答えられません。 –

14

実際には、このような仮説的なis_generator_function()が本当に役に立つかどうかは不思議です。考えてみましょう:

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

is_generator_function()bazquuxは何を返す必要がありますか? baz()はジェネレータを返しますが、それ自身ではありません。quux()はジェネレータを返すかもしれないし、そうでないかもしれません。

+1

@Greg、絶対に - 呼び出し可能なものは、イテレータ(特にイテレータやスーパージェネレータ、あるいはそうでないかもしれない)、または何か他のものを返すかもしれません。引数、乱数、または月の位相に依存します。ジェネレータ関数は、「起きるのではなく何かを返すなら、ジェネレータオブジェクトを返す」という呼び出し可能な関数です(一方、その条件を満たすジェネレータ関数はすべてジェネレータ関数ではありません)。したがって、ユースケースはまとまりにくいです。 –

+0

あなたはそうです、これは仮説的な質問の一種です。私がデビッド・ビーズリーのコルーチンに関するプレゼンテーションを読んでいる間に来た:http://www.dabeaz.com/coroutines/ – Carlos

+10

False、True、False、False - それぞれ。関数がジェネレータインスタンスを返すかどうかについてではなく、ジェネレータ関数かどうかについてです。 – wim

1

に新しいです。

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 

これは、デコレータ関数がunconditionnalyという名前で呼び出されるため動作します。これはテストされる戻り値です。


EDIT:はロバートLujoによってコメントに続き、私のようなものになってしまった:

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

私も同様のケースがあり、エラーが発生しました。SyntaxError:ジェネレータの内部で引数を指定して 'return'を返します。私はそれについて考えると、それは論理的に見えます、同じ関数は、通常の関数とジェネレータ関数は同時にできません。これはあなたのケースで本当に機能しますか? –

関連する問題