brasse said:
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.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'