Python's context managers are a very neat way of handling code that needs a teardown once you are done. Python objects have do have a destructor method (
One thing that is important, and that got me just now, is error handling. I made the mistake of ignoring all those 'junk' arguments (
So what happens now if you do
Of course, YOU know that this code will error out - you can't subtract strings. But if you run this code, it will FAIL SILENTLY. This is because I was careless and did not consider what happens if there is an error SOMEWHERE ELSE.
The proper way to do this, as a minimum, is to change the code to
Another note: defining both a
__del__
) called right before the last instance of the object is about to be destroyed. You can do a teardown there. However there is a lot of fine print to the __del__
method. A cleaner way of doing tear-downs is through Python's context manager, manifested as the with
keyword.class CrushMe: def __init__(self): self.f = open('test.txt', 'w') def foo(self, a, b): self.f.write(str(a - b)) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() return True with CrushMe() as c: c.foo(2, 3)
One thing that is important, and that got me just now, is error handling. I made the mistake of ignoring all those 'junk' arguments (
exc_type, exc_val, exc_tb
). I just skimmed the docs and what popped out is that you need to return True or False depending on whether there was an error. So I wrote my code to return False if there was a problem in saving the file (My actual code is a little more involved, but the same in spirit) and True otherwise.So what happens now if you do
with CrushMe() as c: c.foo('2', '3')
Of course, YOU know that this code will error out - you can't subtract strings. But if you run this code, it will FAIL SILENTLY. This is because I was careless and did not consider what happens if there is an error SOMEWHERE ELSE.
The proper way to do this, as a minimum, is to change the code to
def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() return True if exc_type is None else False
Another note: defining both a
__del__
method and an __exit__
method can lead to tricky situations. The following code, for, instance, calls the close method twice.class CrushMe: def __init__(self): self.f = open('test.txt', 'w') def foo(self, a, b): self.f.write(str(a - b)) def __del__(self): self.close() def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() return True if exc_type is None else False def close(self): print 'Called!' self.f.close() with CrushMe() as c: c.foo(2, 3)
close
gets called first by __exit__
when we exit the context and then when we exit the interpreter and the object is deleted.
Comments
Post a Comment