This recipe introduces a __finalize__ method along with a __finalattrs__ class attribute which can be used for simple finalization without all the complications that __del__ currently creates.
Simply list any instance attributes that need to be available for finalization in the __finalattrs__ class attribute. When the instance is garbage collected, the __finalize__ method will be called with an object exposing just the __finalattrs__ attributes.
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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #!/usr/bin/env python
"""
>>> class A(Finalized):
... __finalattrs__ = 'count',
... items = []
... def __init__(self):
... super(A, self).__init__()
... self.count = 0
... def increment(self):
... self.count += 1
... def clear(self):
... self.count = 0
... def __finalize__(self):
... super(A, self).__finalize__()
... # access to final attributes
... print 'Finalizing:', self.count
... # access to methods
... self.clear()
... # access to class attributes
... self.items.append(self.count)
...
>>> a = A()
>>> a.increment()
>>> a.increment()
>>> a.count
2
>>> A.items
[]
>>> del a
Finalizing: 2
>>> A.items
[0]
>>> # inheritance works as normal
>>> class B(A):
... __finalattrs__ = 'foo', 'bar'
... def __init__(self, foo, bar):
... super(B, self).__init__()
... self.foo = foo
... self.bar = bar
... def __finalize__(self):
... super(B, self).__finalize__()
... print 'Removing:', self.foo, self.bar
...
>>> b = B(42, 'badger')
>>> b.increment()
>>> del b
Finalizing: 1
Removing: 42 badger
"""
import weakref as _weakref
class _FinalizeMetaclass(type):
# list of weak references; when the objects they reference
# disappear, their callbacks will remove them from this set
_reflist = set()
def __init__(cls, name, bases, class_dict):
super(_FinalizeMetaclass, cls).__init__(name, bases, class_dict)
# add a descriptor for each final attribute that
# delegates to the __base__ object
for attr_name in class_dict.get('__finalattrs__', []):
setattr(cls, attr_name, _FinalAttributeProxy(attr_name))
def __call__(cls, *args, **kwargs):
# create a new instance, set __base__, then initialize
obj = cls.__new__(cls, *args, **kwargs)
base = object.__new__(cls)
obj.__base__ = base.__base__ = base
obj.__init__(*args, **kwargs)
# a closure for calling the class's __finalize__ method,
# passing it just the instance's base object
def get_finalize_caller(cls, base):
def finalize_caller(ref):
_FinalizeMetaclass._reflist.remove(ref)
cls.__finalize__(base)
return finalize_caller
# register a weak reference to call the __finalize__
finalize_caller = get_finalize_caller(obj.__class__, obj.__base__)
_FinalizeMetaclass._reflist.add(_weakref.ref(obj, finalize_caller))
# return the newly created instance
return obj
class _FinalAttributeProxy(object):
"""Redirects all {get,set,del}attr calls to the __base__ object"""
def __init__(self, name):
self.base_name = '_base_' + name
def __get__(self, obj, objtype):
return getattr(obj.__base__, self.base_name)
def __set__(self, obj, value):
setattr(obj.__base__, self.base_name, value)
def __delete__(self, obj):
delattr(obj.__base__, self.base_name)
class Finalized(object):
__metaclass__ = _FinalizeMetaclass
__finalattrs__ = ()
def __finalize__(self):
"""Release any resources held by the object.
Only methods, class attributes, and the list of instance
attributes named in the class's __finalattrs__ will be
available from this method.
"""
if __name__ == '__main__':
import doctest
doctest.testmod()
|
This is basically a combination of the recipes:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/519610 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/519621
The former has the disadvantage of having to mark all methods you'd like to use during finalization with a @coremethod decorator. The latter has the disadvantage that after you call bind_finalizer(), any updates to the values of your attributes will not be reflected at the time of finalization.
With this recipe, all class attributes (including methods) are available to the finalizer without need for decorators because the __base__ object is just another instance of the same class. And by using descriptors to proxy the attributes, the finalizer sees all changes to the final attributes.
[2007-05-13: simplify FinalAttributeProxy code by setting base.__base__ = base]