-
-
Notifications
You must be signed in to change notification settings - Fork 11k
ENH: switch to 'with' statement for open/close file in lib.npyio._savez #6545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
|
||
import sys | ||
import os | ||
import os.path | ||
import contextlib | ||
import re | ||
import itertools | ||
import warnings | ||
|
@@ -599,13 +601,41 @@ def savez_compressed(file, *args, **kwds): | |
""" | ||
_savez(file, args, kwds, True) | ||
|
||
@contextlib.contextmanager | ||
def DeleteOnContextExitNamedTemporaryFile(*args, **kwds): | ||
""" Factory for a NamedTemporaryFile that is | ||
deleted on context exit but not on file close. | ||
|
||
Usefull replacement for NamedTemporaryFile, | ||
as WinNT prevents a file to be opened twice. | ||
|
||
Typical case: | ||
with DeleteOnExitNamedTemporaryFile() as fid: | ||
foo(fid) | ||
fid.close() | ||
# File not deleted here | ||
bar(fid.name) | ||
# File deleted here | ||
""" | ||
# Import deferred for startup time improvement | ||
import tempfile | ||
local_kwds = kwds.copy() | ||
local_kwds['delete'] = False | ||
filename = None | ||
with tempfile.NamedTemporaryFile(*args, **local_kwds) as fid: | ||
filename = fid.name | ||
yield fid | ||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm where is the os.close? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The os.close is automatically performed by the context manager of NamedTemporaryFile at the exit of the with statement (here before line 633). The whole pain came from the fact that NamedTemporaryFile is a factory function not a class, so you can not subclass it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens when an error is raised from yield? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you mean by the caller of DeleteOnContextExitNamedTemporaryFile before the return of "yield fid" then it calls the exit method of fid. That is the purpose of using the context manager NamedTemporaryFile. See The exit method will close but not delete the file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exit method of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I've described the good way of doing it by rewritting class _TemporaryFileWrapper from tempfile.py on my last comment on #6866 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you'd want |
||
if os.path.exists(filename): | ||
os.unlink(filename) | ||
except OSError: | ||
pass | ||
|
||
|
||
def _savez(file, args, kwds, compress, allow_pickle=True, pickle_kwargs=None): | ||
# Import is postponed to here since zipfile depends on gzip, an optional | ||
# component of the so-called standard library. | ||
import zipfile | ||
# Import deferred for startup time improvement | ||
import tempfile | ||
|
||
if isinstance(file, basestring): | ||
if not file.endswith('.npz'): | ||
|
@@ -624,35 +654,20 @@ def _savez(file, args, kwds, compress, allow_pickle=True, pickle_kwargs=None): | |
else: | ||
compression = zipfile.ZIP_STORED | ||
|
||
zipf = zipfile_factory(file, mode="w", compression=compression) | ||
|
||
# Stage arrays in a temporary file on disk, before writing to zip. | ||
|
||
# Since target file might be big enough to exceed capacity of a global | ||
# temporary directory, create temp file side-by-side with the target file. | ||
file_dir, file_prefix = os.path.split(file) if _is_string_like(file) else (None, 'tmp') | ||
fd, tmpfile = tempfile.mkstemp(prefix=file_prefix, dir=file_dir, suffix='-numpy.npy') | ||
os.close(fd) | ||
try: | ||
# Context manager compatible with the 'with' statement | ||
# In python => 2.7 ZipFile class has been corrected | ||
# No more need of contextlib.closing | ||
with contextlib.closing( | ||
zipfile_factory(file, mode="w", compression=compression)) as zipf: | ||
for key, val in namedict.items(): | ||
fname = key + '.npy' | ||
fid = open(tmpfile, 'wb') | ||
try: | ||
with DeleteOnContextExitNamedTemporaryFile() as fid: | ||
fname = key + '.npy' | ||
format.write_array(fid, np.asanyarray(val), | ||
allow_pickle=allow_pickle, | ||
pickle_kwargs=pickle_kwargs) | ||
fid.close() | ||
fid = None | ||
zipf.write(tmpfile, arcname=fname) | ||
except IOError as exc: | ||
raise IOError("Failed to write to %s: %s" % (tmpfile, exc)) | ||
finally: | ||
if fid: | ||
fid.close() | ||
finally: | ||
os.remove(tmpfile) | ||
zipf.write(fid.name, arcname=fname) | ||
|
||
zipf.close() | ||
|
||
|
||
def _getconv(dtype): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something simpler?
This is a common pattern in a lot of tests, e.g, in
test_io.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nb that that's not the right way to write a context manager -- if the with
block raises an exception then the yield will raise that exception and
os.remove won't be called, you still need a try/finally.
On Nov 5, 2015 2:03 PM, "Charles Harris" notifications@github.com wrote:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, same thing rewritten as an official context manager ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@njsmith I believe that for real exception handlers
__exit__
is called, then the exception is reraised.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also often just use a temporary directory for this situation, a context manager for that is pretty simple (and also already exists in python3.x) we really should have it in our test utils
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh we already have one but its not correct ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@juliantaylor See also the discussion at #6660.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this also a reasonable place to use
contextlib.closing
, instead of creating your own class? This seems like exactly the situation that function / decorator exists...(PS sorry for the double comment, the last one was in the wrong place)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wackywendell not here but for the zipfile, yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, sorry, I don't know why I'm having so much trouble putting my comments on the right line.