The well-known way to avoid __del__ (and its problems when the object to be deleted is involved in a reference cycle) is to use a finalization callback on a weakref.
This recipe simply makes it easier to write such "finalizable" objects by allowing to write the finalizer as a __finalize__ method (a bit similar to a __del__ method, but not exactly as we'll see).
The first step is to call the bind_finalizer(*attrs) method on your instance; you must give bind_finalizer a list of attribute names necessary for the finalizer to operate, and it will construct a "ghost object" holding those attributes (for example, if you have a "socket" attribute you want to close, you will include "socket" in the arguments to bind_finalizer, which in turn will bind a "socket" attribute on the ghost object). The ghost object has the same class as the original self, so you will be able to call instance methods and use class variables in the finalizer as well.
Please note: the ghost object is created and its attributes are bound as soon as bind_finalizer() is called, so their values must have been properly initialized. A practical idiom is probably to call bind_finalizer() at the end of your constructor. Creating the ghost ignores any __new__ or __init__ method you have defined, so you don't have to worry about constructor signature or unwanted side effects.
Your __finalize__ method must be written as a normal method, except that it can only access those instance attributes whose names you gave to bind_finalizer. This is because __finalize__ gets a "ghost instance" instead of the original instance.
Finally, there is a remove_finalizer() which does the obvious. Call it when the resources have been somehow freed manually and automatic finalization is not useful anymore.
With this recipe, you have an object-oriented finalization scheme which still works as required when your instance is part of a reference cycle to be broken by the Garbage Collector. Also, it doesn't use metaclasses which makes it easier to re-use if you have your own metaclasses.
You can find hereafter the code for the Finalizeable class, as well as an example TransactionBase class which helps writing objects with transaction-like behaviour and optional automatic rollback on object destruction. (but the interesting stuff is really in Finalizable)
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 | import weakref
class Finalizable(object):
"""
Base class enabling the use a __finalize__ method without all the problems
associated with __del__ and reference cycles.
If you call enable_finalizer(), it will call __finalize__ with a single
"ghost instance" argument after the object has been deleted. Creation
of this "ghost instance" does not involve calling the __init__ method,
but merely copying the attributes whose names were given as arguments
to enable_finalizer().
A Finalizable can be part of any reference cycle, but you must be careful
that the attributes given to enable_finalizer() don't reference back to
self, otherwise self will be immortal.
"""
__wr_map = {}
__wr_id = None
def bind_finalizer(self, *attrs):
"""
Enable __finalize__ on the current instance.
The __finalize__ method will be called with a "ghost instance" as
single argument.
This ghost instance is constructed from the attributes whose names
are given to bind_finalizer(), *at the time bind_finalizer() is called*.
"""
cls = type(self)
ghost = object.__new__(cls)
ghost.__dict__.update((k, getattr(self, k)) for k in attrs)
cls_wr_map = cls.__wr_map
def _finalize(ref):
try:
ghost.__finalize__()
finally:
del cls_wr_map[id_ref]
ref = weakref.ref(self, _finalize)
id_ref = id(ref)
cls_wr_map[id_ref] = ref
self.__wr_id = id_ref
def remove_finalizer(self):
"""
Disable __finalize__, provided it has been enabled.
"""
if self.__wr_id:
cls = type(self)
cls_wr_map = cls.__wr_map
del cls_wr_map[self.__wr_id]
del self.__wr_id
class TransactionBase(Finalizable):
"""
A convenience base class to write transaction-like objects,
with automatic rollback() on object destruction if required.
"""
finished = False
def enable_auto_rollback(self):
self.bind_finalizer(*self.ghost_attributes)
def commit(self):
assert not self.finished
self.remove_finalizer()
self.do_commit()
self.finished = True
def rollback(self):
assert not self.finished
self.remove_finalizer()
self.do_rollback(auto=False)
self.finished = True
def __finalize__(ghost):
ghost.do_rollback(auto=True)
class TransactionExample(TransactionBase):
"""
A transaction example which close()s a resource on rollback
"""
ghost_attributes = ('resource', )
def __init__(self, resource):
self.resource = resource
self.enable_auto_rollback()
def __str__(self):
return "ghost-or-object %s" % object.__str__(self)
def do_commit(self):
pass
def do_rollback(self, auto):
if auto:
print "auto rollback", self
else:
print "manual rollback", self
self.resource.close()
|
Generally, this seems like a nice approach, but it should be made a little more obvious that attributes are collected at the time of the enable_finalizer() call. A naive subclass might do something like:
And wonder why 'foo' and 'bar' weren't released. Perhaps the name should be changed from enable_finalizer() to store_finalizer_attributes() or something like that.
corrected. Thanks for the comment! I've changed the API from enable_finalizer/disable_finalizer to bind_finalizer/remove_finalizer. Also, I've made clear in the doc that the attributes are bound when bind_finalizer() is called.
Antoine.