This is an ever-so-slightly over engineered solution to using weakrefs instead of __del__. It provides a "core" object for all the attributes your cleanup code needs, then allows your main object to continue as normal by using descriptors.
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | #!/usr/bin/env python
"""
>>> class Demo(safedel):
... __coreattrs__ = ['count']
... bird = 'European'
... def __init__(self):
... super(Demo, self).__init__()
... self.count = 5
... @coremethod
... def __safedel__(core):
... super(Demo.__coreclass__, core).__safedel__()
... print "Count:", core.count
... @coremethod
... def bridgekeeper(core):
... # core instances can also access class attributes of the
... # original class
... return "%s swallow" % core.bird
>>> d = Demo()
# The core can still access attributes of the main class
>>> print d.bridgekeeper()
European swallow
>>> Demo.bird = 'African'
>>> print d.bridgekeeper()
African swallow
# Any attribute listed in __coreattr__ goes to the core object instead
>>> print d.count
5
>>> d.count = 3
>>> del d
Count: 3
"""
import weakref
__all__ = ['safedel', 'coremethod']
try:
reflist = set() # python2.4. More efficient
except:
class FakeSet(dict):
def add(self, item):
self[item] = None
def remove(self, item):
del self[item]
reflist = FakeSet() # Python2.2 or Python2.3
class SafedelMetaclass(type):
def __init__(cls, name, bases, newattrs):
#super(SafedelMetaclass, cls).__init__(name, bases, newattrs)
type.__init__(name, bases, newattrs)
attrs = {}
for base in bases:
for attrname in getattr(base, '__coreattrs__', []):
attrs[attrname] = None
for attrname in newattrs.get('__coreattrs__', []):
attrs[attrname] = None
def coregetattr(core, name):
return getattr(core.__surfaceclass__, name)
corebases = []
for base in bases:
x = getattr(base, '__coreclass__', None)
if x is not None:
corebases.append(x)
corebases = tuple(corebases)
newcoreattrs = {}
newcoreattrs['__surfaceclass__'] = cls
newcoreattrs['__getattr__'] = coregetattr
for key, value in newattrs.items():
if isinstance(value, coremethod):
# coremethods need to know what name they have, but they
# don't trust the info available when created, so we
# supply it here instead.
value.name = key
newcoreattrs[key] = value.func
coreclass = type(name + '__Core', corebases, newcoreattrs)
cls.__coreclass__ = coreclass
for attrname in attrs:
setattr(cls, attrname, CoreAttrProxy(attrname))
class CoreAttrProxy(object):
__slots__ = ['name']
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype):
if obj is None: # Called from the class, somehow...
raise AttributeError, 'Attribute %s not found' % self.name
return object.__getattribute__(obj.__core__, self.name)
def __set__(self, obj, value):
object.__setattr__(obj.__core__, self.name, value)
def __delete__(self, obj):
# Slots erroneously don't raise AttributeError if the attribute
# doesn't exist, so we use getattr to raise AttributeError anyway.
getattr(obj.__core__, self.name)
object.__delattr__(obj.__core__, self.name)
class coremethod(object):
__slots__ = ['func', 'name']
# name is set by SafedelMetaclass
def __init__(self, func):
try:
if func.im_self is None:
func = func.im_func # Skip through unbound methods
except AttributeError:
pass # Not a method at all
self.func = func
def __get__(self, obj, objtype):
if obj is None:
return self.func
else:
def boundcoremethod(*args, **kwargs):
return getattr(obj.__core__, self.name)(*args, **kwargs)
return boundcoremethod
class safedel(object):
__metaclass__ = SafedelMetaclass
def __init__(self, *args, **kwargs):
super(safedel, self).__init__(*args, **kwargs)
self.__core__ = self.__coreclass__()
def outer(core, cls):
def inner(ref):
reflist.remove(ref)
try:
cls.__safedel__(core)
except:
import traceback
traceback.print_exc()
return inner
reflist.add(weakref.ref(self, outer(self.__core__, self.__class__)))
def __safedel__(core):
f = getattr(super(safedel.__coreclass__, core), '__safedel__', None)
if f:
f.__safedel__()
__safedel__ = coremethod(__safedel__)
def _test():
import doctest
doctest.testmod()
if __name__ == '__main__':
_test()
|
The problems with __del__ are well known: CPython won't delete any objects involved in a cycle if they contain a __del__ method. This is due to the inherent complexity in resurrecting an object so it can perform cleanup. The alternative approach is to ensure the cleanup code was never in the cycle in the first place, and that's what weakref can provide.
Here I provide an "ultimate solution". The metaclass does all the work, all you need to do is slap on __coreattrs__, define a __safedel__() method, and use @coremethod where necessary.
If defining a type that can also be explicitly cleaned up you should have an idempotent cleanup() coremethod method that is called by your __safedel__().
Warning: although safedel class should work in Python 2.2 and 2.3, the doctests included do not. Caveat emptor.
Also, I wrote this code years ago, and although I've cleaned it up before posting, I fully expect someone to point out a glaring oversight. :)
coremethod.__get__. Seems like coremethod.__get__ could just be:
CoreAttrProxy. CoreAttrProxy also seems overly complex. The following still passes your doctests:
merging safedel.__init__ into SafedelMetaclass. I like pushing the logic out of safedel and into the metaclass. Here's one way to do that:
With this approach, you don't even really need the safedel class (though it's probably handy as a simple base class).
This has the unfortunate property of not working when two objects using this compare and hash equal to each other. This is because
weakref.ref
inherits the hash and equality from its referrent.