Cleaning up after failing to contructing objects

Lie Ryan lie.1296 at gmail.com
Thu Jul 9 19:30:02 CEST 2009


brasse wrote:
> Hello!
> 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. The following is an
> example of one possible way to do it:

First, your automatic cleaning Bar() silently pass an unitialized Bar()
into the calling code. When a code fails, it should fail as loud as
possible. Bar() should raises an Exception and the calling code should
turn into something like:

try:
    bar = Bar()
except Exception, e:
    print 'bar failed to construct'

And about the cleaning up, how about:

class CleanWrap(object):
    def __init__(self, cls, cleanup):
        self.cls = cls
        self.cleanup = cleanup
    def __call__(self, *args, **kargs):
        try:
            return self.cls(*args, **kargs)
        except:
            self.cleanup()
            raise

class Bar(object):
    def __init__(self):
        CleanWrappedFoo = CleanWrap(Foo, self.close)
        self.a = CleanWrappedFoo('a')
        self.b = CleanWrappedFoo('b', fail=True)
    def close(self):
        if hasattr(self, 'a'):
            self.a.close()
        if hasattr(self, 'b'):
            self.b.close()

try:
    bar = Bar()
except Exception, e:
    print 'Bar() failed to construct'

==========
My next attempt is pretty neat, using a totally different approach, see
the docstring for details:

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

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

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

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


class CleanList(object):
    ''' Each CleanList() instance is rigged so that if exceptions happen
        in the same CleanList() instance's wrapped class, all objects
        created from the wrapped classes in the same CleanList()
        instance will be cleaned (see "Usage" for much better
        explanation).

        Usage:
        >>> cleaner = CleanList()
        >>> othercleaner = CleanList()
        >>> F = cleaner(F, F.close)
        >>> G = cleaner(G, G.close)
        >>> H = othercleaner(H, H.close)
        >>> a = F()
        >>> b = F()
        >>> c = G()
        >>> d = H()
        >>> cleaner.cleanall()

        cleaner.cleanall() will clean a, b, and c but not d
        exceptions in (F|G|H).__init__ will trigger
        cleaner.cleanall()

        Can be subclassed if you want to override the conditions
        that determines the triggering of cleanups
    '''
    def wrap(self, cls):
        ''' Wrapper factory that customizes Wrapper's subclass
        '''
        class Wrapper(cls):
            ''' Wraps the class' __init__ with cleanup guard.
                Subclasses cls to simulate cls as close as possible
            '''

            # change __class__ to make printing prettier
            # e.g. Foo1.__init__ instead of Wrapper.__init__
            # probably should be removed
            # I'm not sure of the side effects of changing __class__
            __class__ = cls

            def __init__(self_in, *args, **kargs):
                try:
                    sup = super(Wrapper, self_in)
                    ret = sup.__init__(*args, **kargs)
                except:
                    self.cleanall()
                    raise
                else:
                    self.add_to_list(cls, self_in)
                    return ret
        return Wrapper

    def __init__(self):
        self.cleaners = {}

    def __call__(self, cls, cleanup):
        ''' wraps the class constructor '''
        # cleanup, []:
        # cleanup is the function called to clean
        # [] is the object list for that `cleanup` function
        # may not be the best data structure, but it works...
        self.cleaners[cls] = cleanup, []
        return self.wrap(cls)

    def cleanall(self):
        ''' clean all objects '''
        for cleaner, insts in self.cleaners.values():
            for inst in insts:
                cleaner(inst)

    def add_to_list(self, cls, inst):
        ''' add objects to the cleanup list '''
        self.cleaners[cls][1].append(inst)

class Bar(object):
    def __init__(self):
        clist = CleanList()
        otherclist = CleanList()
        CleanFoo1 = clist(Foo1, Foo1.close)
        CleanFoo2 = clist(Foo2, Foo2.close)
        OtherCleanFoo1 = otherclist(Foo1, Foo1.close)
        self.a = CleanFoo1('a')
        self.b = CleanFoo2('b')
        # self.c should not be close()ed
        self.c = OtherCleanFoo1('c')
        self.d = CleanFoo1('d', fail=True)
        self.e = CleanFoo2('e')

class Car(object):
    def __init__(self):
        Clean = CleanList()
        CleanFoo1 = Clean(Foo1, Foo1.close)
        CleanFoo2 = Clean(Foo2, Foo2.close)
        self.a = CleanFoo1('a')
        self.b = CleanFoo2('b')
        self.c = CleanFoo1('c')
        self.d = CleanFoo2('d')

try:
    bar = Car()
except Exception, e:
    print e
    print 'Car() failed to construct'
print
try:
    bar = Bar()
except Exception, e:
    print e
    print 'Bar() failed to construct'



More information about the Python-list mailing list