How to force deletion of a python object?
I am curious about the details of __del__
in python, when and why it should be used and what it shouldn't be used for. I've learned the hard way that it is not really like what one would naively expected from a destructor, in that it is not the opposite of __new__
/ __init__
.
class Foo(object):
def __init__(self):
self.bar = None
def open(self):
if self.bar != 'open':
print 'opening the bar'
self.bar = 'open'
def close(self):
if self.bar != 'closed':
print 'closing the bar'
self.bar = 'close'
def __del__(self):
self.close()
if __name__ == '__main__':
foo = Foo()
foo.open()
del foo
import gc
gc.collect()
I saw in the documentation that it is not guaranteed __del__()
methods are called for objects that still exist when the interpreter exits.
- how can it be guaranteed that for any
Foo
instances existing when the interpreter exits, the bar is closed? - in the code snippet above does the bar get closed on
del foo
or ongc.collect()
... or neither? if you want finer control of those details (e.g. the bar should be closed when the object is unreferenced) what is the usual way to implement that? - when
__del__
is called is it guaranteed that__init__
has already been called? what about if the__init__
raised?
Solution 1:
The way to close resources are context managers, aka the with
statement:
class Foo(object):
def __init__(self):
self.bar = None
def __enter__(self):
if self.bar != 'open':
print 'opening the bar'
self.bar = 'open'
return self # this is bound to the `as` part
def close(self):
if self.bar != 'closed':
print 'closing the bar'
self.bar = 'close'
def __exit__(self, *err):
self.close()
if __name__ == '__main__':
with Foo() as foo:
print foo, foo.bar
output:
opening the bar
<__main__.Foo object at 0x17079d0> open
closing the bar
2) Python's objects get deleted when their reference count is 0. In your example the del foo
removes the last reference so __del__
is called instantly. The GC has no part in this.
class Foo(object):
def __del__(self):
print "deling", self
if __name__ == '__main__':
import gc
gc.disable() # no gc
f = Foo()
print "before"
del f # f gets deleted right away
print "after"
output:
before
deling <__main__.Foo object at 0xc49690>
after
The gc
has nothing to do with deleting your and most other objects. It's there to clean up when simple reference counting does not work, because of self-references or circular references:
class Foo(object):
def __init__(self, other=None):
# make a circular reference
self.link = other
if other is not None:
other.link = self
def __del__(self):
print "deling", self
if __name__ == '__main__':
import gc
gc.disable()
f = Foo(Foo())
print "before"
del f # nothing gets deleted here
print "after"
gc.collect()
print gc.garbage # The GC knows the two Foos are garbage, but won't delete
# them because they have a __del__ method
print "after gc"
# break up the cycle and delete the reference from gc.garbage
del gc.garbage[0].link, gc.garbage[:]
print "done"
output:
before
after
[<__main__.Foo object at 0x22ed8d0>, <__main__.Foo object at 0x22ed950>]
after gc
deling <__main__.Foo object at 0x22ed950>
deling <__main__.Foo object at 0x22ed8d0>
done
3) Lets see:
class Foo(object):
def __init__(self):
raise Exception
def __del__(self):
print "deling", self
if __name__ == '__main__':
f = Foo()
gives:
Traceback (most recent call last):
File "asd.py", line 10, in <module>
f = Foo()
File "asd.py", line 4, in __init__
raise Exception
Exception
deling <__main__.Foo object at 0xa3a910>
Objects are created with __new__
then passed to __init__
as self
. After a exception in __init__
, the object will typically not have a name (ie the f =
part isn't run) so their ref count is 0. This means that the object is deleted normally and __del__
is called.
Solution 2:
In general, to make sure something happens no matter what, you use
from exceptions import NameError
try:
f = open(x)
except ErrorType as e:
pass # handle the error
finally:
try:
f.close()
except NameError: pass
finally
blocks will be run whether or not there is an error in the try
block, and whether or not there is an error in any error handling that takes place in except
blocks. If you don't handle an exception that is raised, it will still be raised after the finally
block is excecuted.
The general way to make sure a file is closed is to use a "context manager".
http://docs.python.org/reference/datamodel.html#context-managers
with open(x) as f:
# do stuff
This will automatically close f
.
For your question #2, bar
gets closed on immediately when it's reference count reaches zero, so on del foo
if there are no other references.
Objects are NOT created by __init__
, they're created by __new__
.
http://docs.python.org/reference/datamodel.html#object.new
When you do foo = Foo()
two things are actually happening, first a new object is being created, __new__
, then it is being initialized, __init__
. So there is no way you could possibly call del foo
before both those steps have taken place. However, if there is an error in __init__
, __del__
will still be called because the object was actually already created in __new__
.
Edit: Corrected when deletion happens if a reference count decreases to zero.
Solution 3:
Perhaps you are looking for a context manager?
>>> class Foo(object):
... def __init__(self):
... self.bar = None
... def __enter__(self):
... if self.bar != 'open':
... print 'opening the bar'
... self.bar = 'open'
... def __exit__(self, type_, value, traceback):
... if self.bar != 'closed':
... print 'closing the bar', type_, value, traceback
... self.bar = 'close'
...
>>>
>>> with Foo() as f:
... # oh no something crashes the program
... sys.exit(0)
...
opening the bar
closing the bar <type 'exceptions.SystemExit'> 0 <traceback object at 0xb7720cfc>