The anatomy of a hack to get around try:/finally: and generators

July 26, 2009

Suppose that you have the Python try:/finally: problem and need to get around it, specifically you need to both be a generator and have your finally: run immediately. Taken from here, one answer is:

def foo(bar):
  try:
    def output():
      ... compute with bar ...
        yield res
      ...

    return output()
  finally:
    print "finalizing"

(Now, there is a lot of things that this doesn't help with; the actual computation hasn't finished, so you can't do anything in the finally: that would destroy its ability to work.)

I find this confusing enough that it's worthwhile walking through why it works. It goes like this:

The output() function is two things; it is both a generator and a closure. As a closure, it captures the arguments that foo() was called with, so that it can do computation with them. As a generator, calling it doesn't run any of its code but instead causes Python to immediately return an iterator object that is in turn effectively capturing the output() closure. The foo() function then returns this iterator object and as part of returning, executes its own finally: clause.

(The shorter way of putting this is that returning the result of calling a generator function does not make you a generator function yourself, and thus your finally: will run normally and immediately.)

Obviously, you don't have to move all of the code into the output() closure. In some situations you might have a lot of things that you do before you can return the first result, and the more you do in the main function the more resources you can release when it returns.

Written on 26 July 2009.
« When code in generators runs
Why you should do code reviews for sysadmin scripts »

Page tools: View Source.
Search:
Login: Password:

Last modified: Sun Jul 26 01:22:34 2009
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.