Skip to content

ENH: switch to 'with' statement for open/close file in lib.npyio._savez - #6545

Closed
rherault-pro wants to merge 1 commit into
numpy:masterfrom
rherault-pro:withstatementinsavez
Closed

rherault-pro wants to merge 1 commit into
numpy:masterfrom
rherault-pro:withstatementinsavez

Conversation

@rherault-pro

Copy link
Copy Markdown

The function _savez stages data during archiving through temporary
files. Before that commit these temp and zip files were handled through
old open/close style. This commits switches to 'with' statement.
which is the recommendation since python 2.5. It's simplifying A LOT
exception handling and garbage collection.

Due to uncompatibility between zipfile in python2.6 and with statement,
ZipFile class has been subclassed to add needed stub methods.

Comment thread numpy/lib/npyio.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think your context handler is exception safe, so either needs a fix or this should still be in a try..except clause.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, exception handler will reraise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the exception handler was only here to remove/close files in the original version, no need to add try/except mechanism to the with statement.
This this exactly the case given in the "Motivation and summary" section of pep-0343 .

@rherault-pro

Copy link
Copy Markdown
Author

I've got a problem with test_load_refcount that randomly fails even on the master branch
#6571

So sometimes Travis check fails or not with the same exact code.

Comment thread numpy/lib/npyio.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something simpler?

@contextmanager
def TempFileName(suffix='', prefix='tmp', dir=None):
    fid, name = mkstemp(suffix, prefix, dir)
    os.close(fid)
    yield name
    os.remove(name)

This is a common pattern in a lot of tests, e.g, in test_io.py

Copy link
Copy Markdown
Member

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" [email protected] wrote:

In numpy/lib/npyio.py
#6545 (comment):

@@ -599,13 +611,41 @@ def savez_compressed(file, _args, *_kwds):
"""
_savez(file, args, kwds, True)

[email protected]

Maybe something simpler?

@contextmanager
def TempFileName(suffix='', prefix='tmp', dir=None):
fid, name = mkstemp(suffix, prefix, dir)
os.close(fid)
yield name
os.remove(name)

This is a common pattern in a lot of tests, e.g, in test_io.py

—
Reply to this email directly or view it on GitHub
https://github.com/numpy/numpy/pull/6545/files#r44076176.

Copy link
Copy Markdown
Member

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 ;)

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

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 ...

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor

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)

Copy link
Copy Markdown
Author

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

Copy link
Copy Markdown
Contributor

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.

Comment thread numpy/lib/npyio.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm where is the os.close?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when an error is raised from yield?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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
https://www.python.org/dev/peps/pep-0343

The exit method will close but not delete the file.
After that our context manager takes the relay and deletes the file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exit method of NamedTemporaryFile, which closes the file but doesn't delete it. The __exit__ method will then reraise the error and you will not catch it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd want ExitStack here to implement this properly

@rherault-pro
rherault-pro force-pushed the withstatementinsavez branch 8 times, most recently from f9a31c2 to 0583fd8 Compare November 24, 2015 20:48
@charris

charris commented Dec 20, 2015

Copy link
Copy Markdown
Member

I've committed a temppath context manager in #6866. If you look there, I also refactor a test to use it. You can get at it (and tempdir) by rebasing on master and importing from numpy.testing. tempdir may be easier to use here.

@rherault-pro

Copy link
Copy Markdown
Author

Thank you, but as I have replied on #6866, the next step is to replace in-disk tempfile by possibly in-mem tempfile (at user choice) for npz saving; returning a file path prevents that.
Moreover, that context manager is in the test part of the source, it may not be cleaned to use it here in core part of the source.

@charris

charris commented Dec 20, 2015

Copy link
Copy Markdown
Member

it may not be cleaned to use it here in core part of the source.
? it is distributed with the rest. Other projects import from numpy.testing.

Note that we are dropping Python 2.6 after 1.11 is branched, so it might make sense to postpone this until then so that the 2.6 code can be removed.

@rherault-pro

Copy link
Copy Markdown
Author

Yes of course less compat code better it is

@homu

homu commented Feb 9, 2016

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #7133) made this pull request unmergeable. Please resolve the merge conflicts.

@rherault-pro
rherault-pro force-pushed the withstatementinsavez branch from 4a18bf7 to c1aa2d8 Compare March 20, 2016 17:23
The function _savez stages data during archiving through temporary
files. Before that commit these temp and zip files were handled through
old open/close style. This commits switches to 'with' statement.
which is the recommendation since python 2.5. It's simplifying A LOT
exception handling and garbage collection.

Due to uncompatibility between zipfile in python2.6 and with statement,
ZipFile class has been subclassed to add needed stub methods.

BUG: WinNT prevents a file to be opened twice

Using contextlib.closing rather than own class
@rherault-pro
rherault-pro force-pushed the withstatementinsavez branch from c1aa2d8 to 7d0bca1 Compare March 20, 2016 21:11
@charris charris mentioned this pull request Nov 5, 2017
@charris

charris commented Nov 5, 2017

Copy link
Copy Markdown
Member

See also #9510.

@charris

charris commented Nov 5, 2017

Copy link
Copy Markdown
Member

OK, going to close this. Python 3.6 allows writing directly to the zipfile with no temporary file needed, so I think that problem will fix itself. For lesser versions of Python we can live with it.

The temppath context manager should probably be made more available.

@rherault-insa Thanks for the PR, even if it didn't get in it led to some useful discussion.

@charris charris closed this Nov 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants