Perhaps someone who knows more about the internals of python and the recent changes in 2.6 and 2.7 can explain this question that came up today in a code review.
I suggest using with
instead of try: ... finally:
to close a file that was written to. Instead of this:
dest = file('foo', 'w')
try:
dest.write('stuff')
finally:
dest.close()
print open('foo').read() # will print 'stuff'
We can use this:
with file('foo', 'w') as dest:
dest.write('stuff')
print open('foo').read() # will print 'stuff'
Why does that work? I'm guessing it's because the file()
instance object has a built in __exit__
method. Is that right?
That means I don't need to use contextlib.closing(thing) right?
For example, suppose you have this class:
class Farm(object):
def __enter__(self):
print "Entering"
return self
def __exit__(self, err_type, err_val, err_tb):
print "Exiting", err_type
self.close()
def close(self):
print "Closing"
with Farm() as farm:
pass
# this will print:
# Entering
# Exiting None
# Closing
Another way to achieve the same specific result would be to use the closing()
decrorator:
class Farm(object):
def close(self):
print "Closing"
from contextlib import closing
with closing(Farm()) as farm:
pass
# this will print:
# Closing
So the closing()
decorator "steals" the __enter__
and __exit__
. This last one can be handy if you do this:
from contextlib import closing
with closing(Farm()) as farm:
raise ValueError
# this will print
# Closing
# Traceback (most recent call last):
# File "dummy.py", line 16, in <module>
# raise ValueError
# ValueError
This is turning into my own loud thinking and I think I get it now. contextlib.closing()
basically makes it possible to do what I did there with the __enter__
and __exit__
and it seems the file()
built-in has a exit handler that takes care of the closing already so you don't have to do it with any extra decorators.
Comments
Correct, contextlib.closing() is just an adapter that maps the context management protocol (i.e. __enter__/__exit__) to any object with a close() method. It's most useful when you can't readily alter the original definition of the objects involved.
If an object already supports the context management protocol directly (which includes most* file-like objects in the standard library), then you don't need the adapter.
(*if we've missed any, then feature requests to fix that are usually looked on quite favourably)
You can also do the same with locks as Lock class also implements context management protocol (it acquires lock on __enter__ and releases it on __exit__)
from threading import Lock
lock = Lock()
def somefunc():
....with lock:
........do_something_within_lock()
(sorry, I had to use dots instead of spaces to indent code because I don't know how to mark it as a code in comment here)
You can always be sure, that the code inside __exit__ method will always be invoked (just like with try-except-finally).