Monday, October 17, 2005

Python: Deeply Nested try/finally Statements

"""This is a convenient way to deeply nest try/finally statements."""

__docformat__ = "restructuredtext"

# Created: Tue Oct 11 08:42:23 PDT 2005
# Author: Shannon -jj Behrens
# Email: jjinux@users.sourceforge.net
#
# Copyright (c) Shannon -jj Behrens. All rights reserved.

from traceback import print_exc


def tryFinally(tasks, handleFinallyException=None):

"""This is a convenient way to deeply nest try/finally statements.

It is appropriate for complicated resource initialization and destruction.
For instance, if you have a list of 50 things that need to get intialized
and later destructed via using try/finally (especially if you need to
create the list dynamically) this function is appropriate.

Given::

tasks = [
((f_enter_0, enter_0_args, enter_0_kargs),
(f_exit_0, exit_0_args, exit_0_kargs)),

((f_enter_1, enter_1_args, enter_1_kargs),
(f_exit_1, exit_1_args, exit_1_kargs)),

((f_enter_2, enter_2_args, enter_2_kargs),
(f_exit_2, exit_2_args, exit_2_kargs))
]

Execute::

f_enter_0(*enter_0_args, **enter_0_kargs)
try:

f_enter_1(*enter_1_args, **enter_1_kargs)
try:

f_enter_2(*enter_2_args, **enter_2_kargs)
try:

pass

finally:
try:
f_exit_2(*exit_2_args, **exit_2_kargs)
except Exception, e:
handleFinallyException(e)

finally:
try:
f_exit_1(*exit_1_args, **exit_1_kargs)
except Exception, e:
handleFinallyException(e)

finally:
try:
f_exit_0(*exit_0_args, **exit_0_kargs)
except Exception, e:
handleFinallyException(e)

tasks
See the example above. Note that you can leave out parts of the tuples
by passing shorter tuples. For instance, here are two examples::

# Second tuple missing.
((f_enter_2, enter_2_args, enter_2_kargs),)

# Leave out args or args and kargs.
((f_enter_2,),
(f_exit_2, exit_2_args))

Don't forget that a tuple of 1 item is written ``(item,)``. This is an
amazingly easy thing to do.

handleFinallyException(e)
This is a callback that gets called if an exception, ``e``, is raised
in a finally block. By default, traceback.print_exc is called.

"""

def defaultFinallyExceptionHandler(e):
print_exc()

def castTwoParts(first):
lenFirst = len(first)
default = ((), ())
max = len(default)
if lenFirst >gt; max:
raise ValueError("""tasks must be a list of tuples of the form (enterTuple, exitTuple).""", first)
return first + default[lenFirst:]

def doNothing(*args, **kargs):
pass

def castFunctionArgsKargs(fTuple):
lenFTuple = len(fTuple)
default = (doNothing, (), {})
max = len(default)
if lenFTuple >gt; max:
raise ValueError("""Each tuple in tasks is a pair of tuples that look like (f, args, kargs).""",
fTuple)
return fTuple + default[lenFTuple:]

if not len(tasks):
return
if not handleFinallyException:
handleFinallyException = defaultFinallyExceptionHandler

first, others = tasks[0], tasks[1:]
first = castTwoParts(first)
first = (castFunctionArgsKargs(first[0]),
castFunctionArgsKargs(first[1]))
((fEnter, fEnterArgs, fEnterKargs),
(fExit, fExitArgs, fExitKargs)) = first

fEnter(*fEnterArgs, **fEnterKargs)
try:
tryFinally(others, handleFinallyException)
finally:
try:
fExit(*fExitArgs, **fExitKargs)
except Exception, e:
handleFinallyException(e)


if __name__ == '__main__':

from cStringIO import StringIO

def printEverything(*args, **kargs): print >gt;>gt;buf, `args`, `kargs`
def refuseArgs(): print >gt;>gt;buf, "refused args"
def raiseValueError(): raise ValueError
def finallyExceptionHandler(e): print >gt;>gt;buf, "caught exception in finally"

tasks = [
((printEverything, ("enter_0_args",), {"in": "in"}),
(printEverything, ("exit_0_args",))),

((printEverything,),
(raiseValueError,)),

((refuseArgs,),)
]

result = """('enter_0_args',) {'in': 'in'}
() {}
refused args
caught exception in finally
('exit_0_args',) {}
"""

buf = StringIO()
tryFinally(tasks, finallyExceptionHandler)
assert buf.getvalue() == result

No comments: