Cleaning up after failing to contructing objects

Mattias Brändström thebrasse at gmail.com
Wed Jul 8 18:43:59 EDT 2009


On Jul 6, 11:15 pm, Scott David Daniels <Scott.Dani... at Acm.Org> wrote:
> brasse wrote:
> > I have been thinking about how write exception safe constructors in
> > Python. By exception safe I mean a constructor that does not leak
> > resources when an exception is raised within it.
>
> ...
>  > As you can see this is less than straight forward. Is there some kind
>  > of best practice that I'm not aware of?
>
> Not so tough.  Something like this tweaked version of your example:
>
> class Foo(object):
>      def __init__(self, name, fail=False):
>          self.name = name
>          if not fail:
>              print '%s.__init__(%s)' % (type(self).__name__, name)
>          else:
>              print '%s.__init__(%s), FAIL' % (type(self).__name__, name)
>              raise ValueError('Asked to fail: %r' % fail)
>
>      def close(self):
>          print '%s.close(%s)' % (type(self).__name__, self.name)
>
> class Bar(object):
>      def __init__(self):
>          unwind = []
>          try:
>              self.a = Foo('a')
>              unwind.append(a)
>              self.b = Foo('b', fail=True)
>              unwind.append(b)
>              ...
>          except Exception, why:
>              while unwind):
>                  unwind.pop().close()
>              raise
>
> bar = Bar()
>

OK. That's another way. I have been thinking about this some more and
right now I am experimenting with a decorator that could help me do
these kinds of things. This is what I have right now:

import functools

class Foo(object):
    def __init__(self, name, fail=False):
        self.name = name
        self.closed = False
        if not fail:
            print '%s.__init__(%s)' % (self.__class__.__name__,
self.name)
        else:
            print '%s.__init__(%s), FAIL' % (self.__class__.__name__,
                                             self.name)
            raise Exception()

    def close(self):
        print '%s.close(%s)' % (self.__class__.__name__, self.name)

def safe(f):
    @functools.wraps(f)
    def wrapper(self, *args, **kwargs):
        variables = []
        def recorder(self, name, value):
            if name != '__class__':
                variables.append(name)
            object.__setattr__(self, name, value)

        class Customized(object): pass
        setattr(Customized, '__setattr__', recorder)
        old_class = self.__class__
        self.__class__ = Customized

        try:
            f(self, *args, **kwargs)
            self.__class__ = old_class
        except:
            # clean up objects created in __init__ (i.e. the call to f
())
            for name in reversed(variables):
                o = getattr(self, name)
                if hasattr(o, 'close'):
                    o.close()
            raise
    return wrapper

class Bar(object):
    @safe
    def __init__(self):
        self.a = Foo('a')
        self.b = Foo('b')
        self.c = Foo('c', fail=True)

bar = Bar()

The safe decorator will record all attributes created on self. If an
exception is raised during the execution of the decorated constructor
it will call close on all the recorded objects. I realize that this
decorator still needs a lot of work before it is usable, but I think
it illustrates the concept. It might even be possible for the
decorator to create a close/cleanup method dynamically.

To have a decorator like this that actually worked would be great
since it would remove the need to write error prone cleanup code. Any
thoughts?

:.:: mattias

> --Scott David Daniels
> Scott.Dani... at Acm.Org




More information about the Python-list mailing list