3

The following code only prints "good". Why the generator function is not executed? I noticed with pdb that after executing 'handlers1' the script reaches the line with f1's definition but then does not get inside the function. Conversely, it's returned 'GeneratorExit: None'.

class foo:

   def f0(self, s):
      print s

   def f1(self, s):
      print "not " + s
      yield 1

   def run(self):
      handlers={0 : self.f0, 1 : self.f1}
      handlers[0]('good')
      handlers[1]('good')

bar = foo()
bar.run()

Why this happens? Is it possible to call generator functions in a similar dynamic way?

Niccolò
  • 2,854
  • 4
  • 24
  • 38

4 Answers4

4

One does not invoke generator functions the same way one invokes normal functions. A generator function, when invoked, does not run but instead returns an iterator. This iterator, when passed to next() or used in other iteration contexts, invokes the original function:

>>> def f1(s):
...   print(s)
...   yield
... 
>>> it = f1("hello")
>>> next(it)
hello
>>> 

To follow up on the discussion in another answer, here is a way to invoke either a regular function or a generator function:

>>> def f0(s):
...   print s
... 
>>> def f1(s):
...   print s
...   yield
... 
>>> try: next(f0("hello"))
... except TypeError: pass
... 
hello
>>> try: next(f1("hello"))
... except TypeError: pass
... 
hello
>>> 
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
3

You need to call next or the code in the generator won't run at all.

class foo:

   def f0(self, s):
      print s

   def f1(self, s):
      print "not " + s
      yield 1

   def run(self):
      handlers={0 : self.f0, 1 : self.f1}
      handlers[0]('good')
      handlers[1]('good').next()

bar = foo()
bar.run()

That prints "good" then "not good".

Shashank
  • 13,713
  • 5
  • 37
  • 63
  • Ok! And could you make something that works for *both* cases? (Imagine to not know whether you are calling a generator or a classic function) – Niccolò Sep 13 '13 at 03:16
  • 1
    @Niccolò Yes you could but you would have to rely on catching exceptions. This follows the "ask for forgiveness rather than permission" theme of Python. Basically you just assume it is a generator first and `try` to call `next()` on it. If that raises an exception, you handle it, and then treat it as a function instead in your handler! – Shashank Sep 13 '13 at 03:25
  • @Niccolò If you try doing `.next()` on a function it will raise something like "AttributeError: 'something' object has no attribute 'next'. So basically you put that code in a `try` block and attempt to handle that exception by calling it as a function instead. – Shashank Sep 13 '13 at 03:27
  • Guys, note that using try and except will run twice any 'normal' function and once the generators. That's because handlers[1]('good').next() runs the function before to execute .next() that is the part the raise the exeption. See: http://stackoverflow.com/questions/1871685/in-python-is-there-a-way-to-check-if-a-function-is-a-generator-function-before – Niccolò Nov 05 '13 at 14:07
1

When you cann a generator function, it only returns an iterator. You should try to call the generator's next method to execute the body of generator function. Try this:

class foo:
   def f0(self, s):
      print s

   def f1(self, s):
      print "not " + s
      yield 1

   def run(self):
      handlers={0 : self.f0, 1 : self.f1}
      for _, func in handlers.iteritems():
          res = func('good')
          if hasattr(res, 'next'):
              next(res)

bar = foo()
bar.run()
Wei Li
  • 471
  • 6
  • 14
  • Ok! And could you make something that works for *both* cases? (Imagine to not know whether you are calling a generator or a classic function) – Niccolò Sep 13 '13 at 03:16
1

The generator function is called, but calling a generator function doesn't immediately do anything. Read the documentation, which explains:

When a generator function is called, the actual arguments are bound to function-local formal argument names in the usual way, but no code in the body of the function is executed.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384