[Python-Dev] "with" use case: replacing a file
Ka-Ping Yee
python-dev at zesty.ca
Thu May 12 22:00:24 CEST 2005
Here's another use case to think about.
Example 2: Replacing a File.
Suppose we want to reliably replace a file. We require that either:
(a) The file is completely replaced with the new contents;
or (b) the filesystem is unchanged and a meaningful exception is thrown.
We'd like to be able to write this conveniently as:
with replace(filename) as file:
...
file.write(spam)
...
file.write(eggs)
...
To make sure the file is never only partially written, we rely on the
filesystem to rename files atomically, so the basic steps (without
error handling) look like this:
tempname = filename + '.tmp'
file = open(tempname, 'w')
...
file.write(spam)
...
file.close()
os.rename(tempname, filename)
We would like to make sure the temporary file is cleaned up and no
filehandles are left open. Here's my analysis of all the execution
paths we need to cover:
1. +open +write +close +rename
2. +open +write +close -rename ?remove
3. +open +write -close ?remove
4. +open -write +close ?remove
5. +open -write -close ?remove
6. -open
(In this list, + means success, - means failure, ? means don't care.)
When i add error handling, i get this:
tempname = filename + '.tmp'
file = open(tempname, 'w') # okay to let exceptions escape
problem = None
try:
try:
...
file.write(spam)
...
except:
problem = sys.exc_info()
raise problem
finally:
try:
file.close()
except Exception, exc:
problem, problem.reason = exc, problem
if not problem:
try:
os.rename(tempname, filename)
except Exception, exc:
problem, problem.reason = exc, problem
if problem:
try:
os.remove(tempname)
except Exception, exc:
problem, problem.reason = exc, problem
raise problem
In this case, the implementation of replace() doesn't require a
separate __except__ method:
class replace:
def __init__(self, filename):
self.filename = filename
self.tempname = '%s.%d.%d' % (self.filename, os.getpid(), id(self))
def __enter__(self):
self.file = open(self.tempname, 'w')
return self
def write(self, data):
self.file.write(data)
def __exit__(self, *problem):
try:
self.file.close()
except Exception, exc:
problem, problem.reason = exc, problem
if not problem: # commit
try:
os.rename(tempname, filename)
except Exception, exc:
problem, problem.reason = exc, problem
if problem: # rollback
try:
os.remove(tempname)
except Exception, exc:
problem, problem.reason = exc, problem
raise problem
This is all so intricate i'm not sure if i got it right. Somebody
let me know if this looks right or not. (In any case, i look forward
to the day when i can rely on someone else to get it right, and they
only have to write it once!)
-- ?!ng
More information about the Python-Dev
mailing list