Welcome, guest | Sign In | My Account | Store | Cart
1

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.

Python, 118 lines
  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]

Add a comment

Sign in to comment