Skip to content
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

MAINT: Raise different type of errors #20389

Merged
merged 7 commits into from
Dec 4, 2021
Merged

Conversation

andrekwr
Copy link
Contributor

Hello,

I added the errors messages as proposed by @WarrenWeckesser in #20329.

@andrekwr andrekwr changed the title BUG: Raise different type of errors MAINT: Raise different type of errors Nov 17, 2021
@mattip
Copy link
Member

mattip commented Nov 17, 2021

Please check your indentation

@mattip
Copy link
Member

mattip commented Nov 18, 2021

The conda build failure is expected.

@WarrenWeckesser
Copy link
Member

WarrenWeckesser commented Nov 18, 2021

Thanks @andrekwr!

Some of those try/except statements aren't doing anything useful, so I think the changes can be simplified quite a bit.

The reason for catching and reraising the TypeError exception in this block of code is to provide the user with a more informative error message about the arguments given to genfromtxt. If we remove the try/except statements completely, the code (with an editorial comment added) looks like:

    if isinstance(fname, os_PathLike):
        fname = os_fspath(fname)
    if isinstance(fname, str):
        fid = np.lib._datasource.open(fname, 'rt', encoding=encoding)
        fid_ctx = contextlib.closing(fid)
    else:
        # If here, we expect fname to be a sequence or iterator of strings.
        fid = fname
        fid_ctx = contextlib.nullcontext(fid)
    fhd = iter(fid)

For many cases, bad input to this code will result in an exception that gives a reasonable error message to the user without having to reraise an exception. Some examples:

  • The file does not exist:
    In [23]: np.genfromtxt('nonesuch.dat')
    ---------------------------------------------------------------------------
    FileNotFoundError                         Traceback (most recent call last)
    <ipython-input-23-05210d43ec13> in <module>
    ----> 1 np.genfromtxt('nonesuch.dat')
    ... snip ...
    FileNotFoundError: nonesuch.dat not found.
    
  • An encoding parameter with the wrong type:
    In [25]: np.genfromtxt('data.txt', encoding=int)
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-25-15f3293acb50> in <module>
    ----> 1 np.genfromtxt('data.txt', encoding=int)
    ... snip ...
    TypeError: open() argument 'encoding' must be str or None, not type
    

The error messages that are not very helpful occur when the first argument is not a string or a path, and also not an iterator, such as an integer or None:

In [26]: np.genfromtxt(12345)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-26-726bcd824d92> in <module>
----> 1 np.genfromtxt(12345)

~/mc39numpy/lib/python3.9/site-packages/numpy/lib/npyio.py in genfromtxt(fname, dtype, comments, delimiter, skip_header, skip_footer, converters, missing_values, filling_values, usecols, names, excludelist, deletechars, replace_space, autostrip, case_sensitive, defaultfmt, unpack, usemask, loose, invalid_raise, max_rows, encoding, like)
  1818         fid = fname
  1819         fid_ctx = contextlib.nullcontext(fid)
-> 1820     fhd = iter(fid)
  1821 
  1822     with fid_ctx:

TypeError: 'int' object is not iterable

In [27]: np.genfromtxt(None)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-27-37c00eace4cb> in <module>
----> 1 np.genfromtxt(None)

~/mc39numpy/lib/python3.9/site-packages/numpy/lib/npyio.py in genfromtxt(fname, dtype, comments, delimiter, skip_header, skip_footer, converters, missing_values, filling_values, usecols, names, excludelist, deletechars, replace_space, autostrip, case_sensitive, defaultfmt, unpack, usemask, loose, invalid_raise, max_rows, encoding, like)
  1818         fid = fname
  1819         fid_ctx = contextlib.nullcontext(fid)
-> 1820     fhd = iter(fid)
  1821 
  1822     with fid_ctx:

TypeError: 'NoneType' object is not iterable

So I think the only code that needs to be wrapped in a try/except statement is the one line fhd = iter(fid). The updated code becomes:

    if isinstance(fname, os_PathLike):
        fname = os_fspath(fname)
    if isinstance(fname, str):
        fid = np.lib._datasource.open(fname, 'rt', encoding=encoding)
        fid_ctx = contextlib.closing(fid)
    else:
        # If here, we expect fname to be a sequence or iterator of strings.
        fid = fname
        fid_ctx = contextlib.nullcontext(fid)
    try:
        fhd = iter(fid)
    except TypeError as e:
        raise TypeError(
            "fname must be a string, a filehandle, a sequence of strings,\n"
            f"or an iterator of strings. Got {type(fname)} instead."
        ) from e

Note that I did some copy-editing in the error message of the reraised TypeError.
Then np.genfromtxt(123) gives the more informative error message:

In [2]: np.genfromtxt(123)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/mc39numpy/lib/python3.9/site-packages/numpy/lib/npyio.py in genfromtxt(fname, dtype, comments, delimiter, skip_header, skip_footer, converters, missing_values, filling_values, usecols, names, excludelist, deletechars, replace_space, autostrip, case_sensitive, defaultfmt, unpack, usemask, loose, invalid_raise, max_rows, encoding, like)
   1819     try:
-> 1820         fhd = iter(fid)
   1821     except TypeError as e:

TypeError: 'int' object is not iterable

The above exception was the direct cause of the following exception:

TypeError                                 Traceback (most recent call last)
<ipython-input-2-096a3b4ec972> in <module>
----> 1 np.genfromtxt(123)

~/mc39numpy/lib/python3.9/site-packages/numpy/lib/npyio.py in genfromtxt(fname, dtype, comments, delimiter, skip_header, skip_footer, converters, missing_values, filling_values, usecols, names, excludelist, deletechars, replace_space, autostrip, case_sensitive, defaultfmt, unpack, usemask, loose, invalid_raise, max_rows, encoding, like)
   1820         fhd = iter(fid)
   1821     except TypeError as e:
-> 1822         raise TypeError(
   1823             "fname must be a string, a filehandle, a sequence of strings,\n"
   1824             f"or an iterator of strings. Got {type(fname)} instead."

TypeError: fname must be a string, a filehandle, a sequence of strings,
or an iterator of strings. Got <class 'int'> instead.

We should add a unit test for this behavior. The tests for genfromtxt are in the class TestFromTxt in the file numpy/lib/tests/test_io.py. We can create a unit test for the error handling by adding a method such as the following to TestFromTxt:

    def test_bad_fname(self):
        with pytest.raises(TypeError, match='fname must be a string,'):
            np.genfromtxt(123)

Copy link
Member

@WarrenWeckesser WarrenWeckesser left a comment

Choose a reason for hiding this comment

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

(Github requires a comment here before I can submit the review with "Request changes" clicked. The requested changes are in my previous comment.)

@andrekwr
Copy link
Contributor Author

andrekwr commented Nov 23, 2021

Hey @WarrenWeckesser , sorry for the delay.
I did those try/except to ensure that everything was being correctly handled, but i think you are right. I just commited the changes you requested.

Copy link
Member

@WarrenWeckesser WarrenWeckesser left a comment

Choose a reason for hiding this comment

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

Thanks @andrekwr.

There are a few whitespace issues that I'll fix, and then I'll merge.

numpy/lib/npyio.py Outdated Show resolved Hide resolved
numpy/lib/npyio.py Outdated Show resolved Hide resolved
numpy/lib/tests/test_io.py Outdated Show resolved Hide resolved
@WarrenWeckesser WarrenWeckesser merged commit a81535a into numpy:main Dec 4, 2021
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.

3 participants