This recipe provides a semi-automatic mechanism to close resources.
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 | import atexit
class AutoCloseMeta(type):
"Tracks the instances and closes them in reverse instantiation order"
def __new__(mcl, name, bases, dic):
cls = super(AutoCloseMeta, mcl).__new__(mcl, name, bases, dic)
cls.closeall # assert the method closeall exists
cls._open_instances = [] # objects not closed yet
return cls
def __call__(cls, *args, **kw):
# tracks the instances of the instances
self = super(AutoCloseMeta, cls).__call__(*args, **kw)
cls._open_instances.append(self)
return self
def closeall(cls):
"Recursively close all instances of cls and its subclasses"
print 'Closing instances of %s' % cls # you may remove this print
for obj in reversed(cls._open_instances):
obj.close()
cls._open_instances.remove(obj)
for subc in cls.__subclasses__():
subc.closeall()
class AutoClose(object):
"Abstract base class"
__metaclass__ = AutoCloseMeta
atexit.register(AutoClose.closeall)
if __name__ == '__main__': # test
import logging
class C(AutoClose):
def __init__(self, id):
self.id = id
def close(self):
logging.warn('closing object %s' % self.id)
class D(C):
pass
c1 = C(1)
c2 = C(2)
d3 = D(3)
|
I have read in my life code where the release of a resource was done in the destructor method, relying on Python reference counting garbage collector. This kind of code is very fragile, since Python does not make any guarantee at all about when the destructor will be called. Consider for instance this code (coming from a real life case):
$ cat deallocating.py
import logging
class C(object):
def __init__(self):
logging.warn('Allocating resource ...')
def __del__(self):
logging.warn('De-allocating resource ...')
print 'THIS IS NEVER REACHED!'
if __name__ == '__main__':
c = C()
$ python deallocating.py
WARNING:root:Allocating resource ...
Exception exceptions.AttributeError: "'NoneType' object has no
attribute 'warn'" in > ignored
The issue here is that the destructor is called too late, when the cleanup mechanism (see http://www.python.org/doc/essays/cleanup) has already set logging to None. This recipe
1) gets rid of the destructor, and put the responsibility of deallocating the resource in a .close method (which is IMO opinion much clearer); 2) used in conjunction with the contextlib.closing class (new in Python 2.5) it allows to perform explicit resource deallocation with the 'with' statement, which is the preferred way to do that in Python 2.5; 3) at the end of the program, it automatically closes the resources which were not closed explicitly before.
The usage is pretty simple. You should just import from the autoclose module the AutoClose class and you should add it to the list of your base classes. Then you should make sure your class provides a .close method.
Here is an example:
$ python autoclose.py
Closing instances of <class '__main__.AutoClose'>
Closing instances of <class '__main__.C'>
WARNING:root:closing object 2
WARNING:root:closing object 1
Closing instances of <class '__main__.D'>
WARNING:root:closing object 3
Notice that AutoClose keeps in memory a potentially large list of instances, therefore your are advised to explicitly close your objects as soon as possible, to keep the list short. You can remove all the instances of a given class (and its subclasses) with the (meta)method .closeall(). The implementation below should answer all your questions about how this is done exactly. In particular the call to atexit.register(AutoClose.closeall) is the one that ensures correct finalization (unlikely destructors methods, functions registered in atexit do not rely on the garbage collector and are called before the cleanup mechanism).