views:

124

answers:

2

Lets say I have two functions:

def foo():
  return 'foo'

def bar():
  yield 'bar'

The first one is a normal function, and the second is a generator function. Now I want to write something like this:

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

What will a straightforward implementation of is_generator_function() look like? Using the types package I can test if gen is a generator, but I wish to do so before invoking func().

Now consider the following case:

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

An invocation of goo() will return a generator. I presume that the python parser knows that the goo() function has a yield statement, and I wonder if it possible to get that information easily.

Thanks!

+5  A: 
>>> 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        
>>>

As you see, the key difference is that the bytecode for bar will contain at least one YIELD_VALUE opcode. I recommend using the dis module (redirecting its output to a StringIO instance and checking its getvalue, of course) because this provides you a measure of robustness over bytecode changes -- the exact numeric values of the opcodes will change, but the disassembled symbolic value will stay pretty stable;-).

Alex Martelli
Alex, how do you feel about calling "blah = func()"... then checking if type(blah) is a generator? and if it's not, then func() was called already :-). I think that would have been how I would have first investigated how to do this :-).
Tom
Was about to write the same but the Python übergod came in first. :-)
paprika
The OP is very clear in the Q's title that he wants the information **before calling** -- showing how to get it **after** calling does not answer the given question with the clearly expressed constraints.
Alex Martelli
@paprika: Ha... I have no idea if this works... but Alex said it does... so +1 :-)... and I doubt anyone else will have a better answer... not even Guido himself.
Tom
@Alex: absolutely... I must say... I didn't read the question thoroughly :-o. Now I'm looking at it and it's obvious :-). I kind of focused on the "run" function and thought that could easily be done with types.
Tom
@paprika, tx for the kudos, but that title belongs to Guido van Rossum (who's not active on SO, AFAIK). In the Python pantheon, I can at most be an Einherjar (gender issues precluding me being a Valkyrie;-).
Alex Martelli
Thank you very much Alex!
Carlos Rocha
+6  A: 

Actually, I'm wondering just how useful such a hypothetical is_generator_function() would be really. Consider:

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

What should is_generator_function() return for baz and quux? baz() returns a generator but isn't one itself, and quux() might return a generator or might not.

Greg Hewgill
@Greg, absolutely -- any callable may return an iterable (which may in particular be an iterator and in super-particular a generator, or may not), or something else (or nothing at all, by raising an exception), depending on arguments, random numbers, or the phase of the moon. A generator function is a callable such that "if it returns anything at all rather than raising, will return a generator object" (meanwhile, not all callables meeting that conditions are gnerator functions). Use cases are therefore hard to concoct.
Alex Martelli
You are right, this is a kind of hypothetical question. It came while I was reading David Beazley's presentation on coroutines: http://www.dabeaz.com/coroutines/
Carlos Rocha