Welcome, guest | Sign In | My Account | Store | Cart

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

Python, 157 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""It's a convenient way to deeply nest try/finally statements."""

__docformat__ = "restructuredtext"

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 > 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 > 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 >>buf, `args`, `kargs`
    def refuseArgs(): print >>buf, "refused args"
    def raiseValueError(): raise ValueError
    def finallyExceptionHandler(e): print >>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

Many people have suggested that newer versions of Python will have "with" statements that will alleviate the need for my library. However, my library is appropriate if you have a long list of initialization tasks that may need to be setup dynamically using input from a subclass. Perhaps I could create a new library based on the "with" statement, but the ability to control a complete list of init/destruct functions is still necessary.

2 comments

Scott David Daniels 18 years, 6 months ago  # | flag

Wouldn't a structure like this work?

final_list = []
try:
    f_enter_0(*enter_0_args, **enter_0_kargs)
    final_list.append(((f_exit_0, exit_0_args, exit_0_kargs))
    ...
    f_enter_1(*enter_1_args, **enter_1_kargs)
    final_list.append(((f_exit_1, exit_1_args, exit_1_kargs))
    ...
    f_enter_2(*enter_2_args, **enter_2_kargs)
    final_list.append(((f_exit_2, exit_2_args, exit_2_kargs))
    ...
finally:
    while final_list:
        try:
            function = elements[0]
            if len(elements) > 2:
                args, kargs = elements[2:]
                function(*args, **kargs)
            elif len(elements) > 1:
                function(*elements[1])
            else:
                function()
        except Exception, e:
            handleFinallyException(e)
Scott David Daniels 18 years, 6 months ago  # | flag

oops -- add to top of final loop. Oops, missing line (pasting in parts):

The final loop should read:

...
finally:
    while final_list:
        try:
            elements = final_list.pop()
            function = elements[0]
            if len(elements) > 2:
...