After discussing my last post with a friend and talking about a few other issues, we came to the conclusion that it would be worthwhile to discuss more gotchas.
First though, what is a gotcha? Wikipedia gives a good definition:
In programming, a gotcha is a feature of a system, a program or a programming language that works in the way it is documented but is counter-intuitive and almost invites mistakes because it is both enticingly easy to invoke and completely unexpected and/or unreasonable in its outcome.
So let’s start with “__del__ is not the opposite of __init__”.
If you come from c++ or a similar background, you are probably well versed in object oriented concepts, specifically, constructors and destructors. The usual expectation is to have the destructor called only for fully constructed objects – i.e., objects whose constructor returned without raising an exception.
If the constructor raises an exception, it is expected to “clean up after itself”, and not expect the destructor to run.
Since in Python __init__ is the de-facto constructor, and __del__ is considered the destructor, most people expect this line of reasoning to work with __init__ and __del__.
This is mistaken. __del__ is not the opposite of __init__, but rather of __new__. Which means that if __init__ raises an exception, then __del__ will still be called.
I’ve run into this issue myself several times in the past. Consider the following sample code:
class A(object): def __init__(self,x): if x == 0: raise Exception() self.x = x def __del__(self): print self.x
This code demonstrates a common case: a constructor that might fail, and a destructor that does something with the instance’s members. If you try to instantiate A with x = 0, you’ll get an exception. This is to be expected.
However, what is less expected is when the partially constructed A is garbage-collected (which may be anytime later, and not necessarily right away):
Exception exceptions.AttributeError: "'A' object has no attribute 'x'" in <bound method A.__del__ of <__main__.A object at 0x02449570>> ignored
What happened is that __del__ was called even though __init__ raised an exception. When __del__ tried to access self.x it got an attribute error, because it hasn’t been defined yet.
1. Don’t use __del__ unless you really have to. I’m going to write a more about it soon.
2. If you do use __del__ make sure you are covered for any case in which __init__ didn’t finish running.