6

I have a class that opens a file for writing. In my destructor, I call the function that closes the file:

class MyClass:
    def __del__(self):
        self.close()

    def close(self):
        if self.__fileHandle__ is not None:
                self.__fileHandle__.close()

but when I delete the object with code like:

myobj = MyClass()
myobj.open()
del myobj

if I try to reinstantiate the object, I get a value error:

ValueError: The file 'filename' is already opened.  Please close it before reopening in write mode.

whereas if I call myobj.close() before del myobj I don't get this problem. So why isn't __del__() getting called?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
tdc
  • 8,219
  • 11
  • 41
  • 63
  • 1
    Try inheriting from `object` - not doing so has been considered out-of-date style for some six years. – Marcin Jan 18 '12 at 09:37
  • You should show some more code. How can one tell you how `myobj.close()` changes things, without knowing what it does? – ugoren Jan 18 '12 at 09:37
  • 1
    Keep in mind that `del` doesn't call `__del__`. It just removes one of the references. Generally it's better to close explicitly, possibly using the context manager protocol (`with` statement). – yak Jan 18 '12 at 09:45
  • 1
    @Marcin - that solved it ... post as an answer? Still curious as to why this should be the case ... – tdc Jan 18 '12 at 09:47

4 Answers4

8

That's not what del does. It's unfortunate that __del__ has the same name as del, because they are not related to each other. In modern terminology, the __del__ method would be called a finalizer, not a destructor and the difference is important.

The short difference is that it's easy to guarantee when a destructor is called, but you have very few guarantees about when __del__ will be called and it might never be called. There are many different circumstances that can cause this.

If you want lexical scoping, use a with statement. Otherwise, call myobj.close() directly. The del statement only deletes references, not objects.

I found another answer (link) to a different question that answers this in more detail. It is unfortunate that the accepted answer to that question contains egregious errors.

Edit: As commentors noted, you need to inherit from object. That is fine, but it is still possible that __del__ will never be called (you could be getting lucky). See the linked answer above.

Community
  • 1
  • 1
Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
8

Are you sure you want to use __del__? There are issues with __del__ and garbage collection.

You could make MyClass a context manager instead:

class MyClass(object):
    def __enter__(self):
        return self
    def __exit__(self,ext_type,exc_value,traceback):
        if self.__fileHandle__ is not None:
                self.__fileHandle__.close()

By doing so, you could use MyClass like this:

with MyClass() as myobj:
    ...

and myobj.__exit__ (and thus self.__fileHandle__.close()) will be called when Python leaves the with-block.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • +1 for mentioning the issues. http://docs.python.org/library/gc.html#gc.garbage says that «Objects that have __del__() methods and are part of a reference cycle cause the entire reference cycle to be uncollectable, including objects not necessarily in the cycle but reachable only from it.» – Mischa Arefiev Jan 18 '12 at 10:22
  • I don't see how there's a reference cycle in the OP's example. There's one object, it gets `del`'d, and `__del__()` doesn't get called. – sudo May 26 '17 at 17:59
  • 1
    Also, in my situation, I'd rather let the GC handle stuff as it doesn't need to all get closed immediately, just eventually... but it always has problems. And no, context managers are not a real solution. They restrict you to closing the object within the same function call and indent your code one level every time you use one. Makes it unreadable when I want to create 20 of these. – sudo May 26 '17 at 18:02
2

Your code should inherit from object - not doing so has been considered out of date (except in special cases) for at least six years.

You can read about __del__ here: http://docs.python.org/reference/datamodel.html#object.del

The short version of why you need to inherit from object is that __del__ is only "magic" on new-style classes.

If you need to rely on calling of a finalizer, I strongly suggest that you use the context manager approach recommended in other answers, because that is a portable, robust solution.

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 1
    Ah just found an instance where this "magic" doesn't work ... using ipython instead. – tdc Jan 18 '12 at 10:21
  • @tdc: Unless I'm mistaken, ipython is based on cpython. In any case, if you rely on the behaviour of a particular instance, your code will be non-portable. It would probably be better to use the context manager approach recommended in other answers. – Marcin Jan 18 '12 at 10:46
0

Perhaps something else is referencing it, which is why __del__ isn't being called (yet).

Consider this code:

#!/usr/bin/env python

import time

class NiceClass():
    def __init__(self, num):
        print "Make %i" % num
        self.num = num
    def __del__(self):
        print "Unmake %i" % self.num

x = NiceClass(1)
y = NiceClass(2)
z = NiceClass(3)
lst = [x, y, z]
time.sleep(1)
del x
del y
del z
time.sleep(1)
print "Deleting the list."
del lst
time.sleep(1)

It doesn't call __del__ of NiceClass instances until we delete the list that references them.

Unlike C++, __del__ isn't being called unconditionally to destruct the object on demand. GC makes things a bit harder. Here is some info: http://arctrix.com/nas/python/gc/

Mischa Arefiev
  • 5,227
  • 4
  • 26
  • 34