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' -- http://mail.python.org/mailman/listinfo/python-list