Repository: numpy/numpy

Issue:
numpy.save rejects unicode paths in Python 2.7
We've discovered this in our tests in [nipy/nipype](https://github.com/nipy/nipype) when 1.16 was released ([failing test](https://circleci.com/gh/nipy/nipype/7570)). It's unclear why it wasn't hitting our `--pre` tests, but it wasn't too tough to track down and reproduce.

It appears to have been introduced in #12157, which dropped an `if isinstance(file, basestring):` and explicitly checks `str` and `bytes`, which will resolve to `str` and `str` in Python 2, not `unicode` and `str`.

This is probably easily fixable on our end (just call `str()`), but in case you intend to support unicode paths, you should know about this.

Related: nipy/nipype#2855

<!-- Please describe the issue in detail here, and fill in the fields below -->

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
from __future__ import unicode_literals
import numpy as np
np.save('abc.npy', np.array([0, 1, 2])
```

<!-- Remove these sections for a feature request -->

### Error message:


```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-109f1bba4449> in <module>()
----> 1 np.save('abc.npy', np.array([0, 1, 2]))

/anaconda3/envs/python27/lib/python2.7/site-packages/numpy/lib/npyio.pyc in save(file, arr, allow_pickle, fix_imports)
    512         fid = file
    513     else:
--> 514         file = os_fspath(file)
    515         if not file.endswith('.npy'):
    516             file = file + '.npy'

/anaconda3/envs/python27/lib/python2.7/site-packages/numpy/compat/py3k.pyc in os_fspath(path)
    235             else:
    236                 raise TypeError("expected str, bytes or os.PathLike object, "
--> 237                                 "not " + path_type.__name__)
    238         if isinstance(path_repr, (str, bytes)):
    239             return path_repr

TypeError: expected str, bytes or os.PathLike object, not unicode
```
<!-- If you are reporting a segfault please include a GDB traceback, which you
can generate by following
https://github.com/numpy/numpy/blob/master/doc/source/dev/development_environment.rst#debugging -->

<!-- Full error message, if any (starting from line Traceback: ...) -->

### Numpy/Python version information:

<!-- Output from 'import sys, numpy; print(numpy.__version__, sys.version)' -->

```
('1.16.0', '2.7.15 |Anaconda, Inc.| (default, Dec 14 2018, 13:10:39) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]')
```


Discussion:

Resolving patch:
diff --git a/numpy/compat/py3k.py b/numpy/compat/py3k.py
--- a/numpy/compat/py3k.py
+++ b/numpy/compat/py3k.py
@@ -219,7 +219,7 @@ def os_fspath(path):
         path representation is not str or bytes, TypeError is raised. If the
         provided path is not str, bytes, or os.PathLike, TypeError is raised.
         """
-        if isinstance(path, (str, bytes)):
+        if isinstance(path, (unicode, bytes)):
             return path
 
         # Work from the object's type to match method resolution of other magic
@@ -235,7 +235,7 @@ def os_fspath(path):
             else:
                 raise TypeError("expected str, bytes or os.PathLike object, "
                                 "not " + path_type.__name__)
-        if isinstance(path_repr, (str, bytes)):
+        if isinstance(path_repr, (unicode, bytes)):
             return path_repr
         else:
             raise TypeError("expected {}.__fspath__() to return str or bytes, "


Test patch:
diff --git a/numpy/compat/tests/test_compat.py b/numpy/compat/tests/test_compat.py
--- a/numpy/compat/tests/test_compat.py
+++ b/numpy/compat/tests/test_compat.py
@@ -2,7 +2,7 @@
 
 from os.path import join
 
-from numpy.compat import isfileobj
+from numpy.compat import isfileobj, os_fspath
 from numpy.testing import assert_
 from numpy.testing import tempdir
 
@@ -19,3 +19,8 @@ def test_isfileobj():
 
         with open(filename, 'rb') as f:
             assert_(isfileobj(f))
+
+
+def test_os_fspath_strings():
+    for string_path in (b'/a/b/c.d', u'/a/b/c.d'):
+        assert_(os_fspath(string_path) == string_path)