summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/_internal.py34
-rw-r--r--numpy/core/shape_base.py65
-rw-r--r--numpy/core/src/multiarray/alloc.c9
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c14
-rw-r--r--numpy/core/src/multiarray/item_selection.c22
-rw-r--r--numpy/distutils/command/config.py33
-rw-r--r--numpy/distutils/fcompiler/ibm.py13
-rw-r--r--numpy/f2py/__init__.py53
-rw-r--r--numpy/f2py/tests/test_quoted_character.py110
-rw-r--r--numpy/lib/npyio.py16
-rw-r--r--numpy/lib/tests/test_arraypad.py15
-rw-r--r--numpy/lib/tests/test_io.py6
-rw-r--r--numpy/ma/core.py6
13 files changed, 321 insertions, 75 deletions
diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py
index 48ede14d0..9b8f5aef3 100644
--- a/numpy/core/_internal.py
+++ b/numpy/core/_internal.py
@@ -1,5 +1,5 @@
"""
-A place for code to be called from core C-code.
+A place for internal code
Some things are more easily handled Python.
@@ -808,3 +808,35 @@ def _is_from_ctypes(obj):
return 'ctypes' in ctype_base.__module__
except Exception:
return False
+
+
+class recursive(object):
+ '''
+ A decorator class for recursive nested functions.
+ Naive recursive nested functions hold a reference to themselves:
+
+ def outer(*args):
+ def stringify_leaky(arg0, *arg1):
+ if len(arg1) > 0:
+ return stringify_leaky(*arg1) # <- HERE
+ return str(arg0)
+ stringify_leaky(*args)
+
+ This design pattern creates a reference cycle that is difficult for a
+ garbage collector to resolve. The decorator class prevents the
+ cycle by passing the nested function in as an argument `self`:
+
+ def outer(*args):
+ @recursive
+ def stringify(self, arg0, *arg1):
+ if len(arg1) > 0:
+ return self(*arg1)
+ return str(arg0)
+ stringify(*args)
+
+ '''
+ def __init__(self, func):
+ self.func = func
+ def __call__(self, *args, **kwargs):
+ return self.func(self, *args, **kwargs)
+
diff --git a/numpy/core/shape_base.py b/numpy/core/shape_base.py
index 319c25088..30919ed7e 100644
--- a/numpy/core/shape_base.py
+++ b/numpy/core/shape_base.py
@@ -7,6 +7,7 @@ __all__ = ['atleast_1d', 'atleast_2d', 'atleast_3d', 'block', 'hstack',
from . import numeric as _nx
from .numeric import array, asanyarray, newaxis
from .multiarray import normalize_axis_index
+from ._internal import recursive
def atleast_1d(*arys):
"""
@@ -360,6 +361,14 @@ def stack(arrays, axis=0, out=None):
return _nx.concatenate(expanded_arrays, axis=axis, out=out)
+def _block_format_index(index):
+ """
+ Convert a list of indices ``[0, 1, 2]`` into ``"arrays[0][1][2]"``.
+ """
+ idx_str = ''.join('[{}]'.format(i) for i in index if i is not None)
+ return 'arrays' + idx_str
+
+
def _block_check_depths_match(arrays, parent_index=[]):
"""
Recursive function checking that the depths of nested lists in `arrays`
@@ -370,19 +379,23 @@ def _block_check_depths_match(arrays, parent_index=[]):
for each innermost list, in case an error needs to be raised, so that
the index of the offending list can be printed as part of the error.
- The parameter `parent_index` is the full index of `arrays` within the
- nested lists passed to _block_check_depths_match at the top of the
- recursion.
- The return value is a pair. The first item returned is the full index
- of an element (specifically the first element) from the bottom of the
- nesting in `arrays`. An empty list at the bottom of the nesting is
- represented by a `None` index.
- The second item is the maximum of the ndims of the arrays nested in
- `arrays`.
+ Parameters
+ ----------
+ arrays : nested list of arrays
+ The arrays to check
+ parent_index : list of int
+ The full index of `arrays` within the nested lists passed to
+ `_block_check_depths_match` at the top of the recursion.
+
+ Returns
+ -------
+ first_index : list of int
+ The full index of an element from the bottom of the nesting in
+ `arrays`. If any element at the bottom is an empty list, this will
+ refer to it, and the last index along the empty axis will be `None`.
+ max_arr_ndim : int
+ The maximum of the ndims of the arrays nested in `arrays`.
"""
- def format_index(index):
- idx_str = ''.join('[{}]'.format(i) for i in index if i is not None)
- return 'arrays' + idx_str
if type(arrays) is tuple:
# not strictly necessary, but saves us from:
# - more than one way to do things - no point treating tuples like
@@ -393,7 +406,7 @@ def _block_check_depths_match(arrays, parent_index=[]):
'{} is a tuple. '
'Only lists can be used to arrange blocks, and np.block does '
'not allow implicit conversion from tuple to ndarray.'.format(
- format_index(parent_index)
+ _block_format_index(parent_index)
)
)
elif type(arrays) is list and len(arrays) > 0:
@@ -410,9 +423,12 @@ def _block_check_depths_match(arrays, parent_index=[]):
"{}, but there is an element at depth {} ({})".format(
len(first_index),
len(index),
- format_index(index)
+ _block_format_index(index)
)
)
+ # propagate our flag that indicates an empty list at the bottom
+ if index[-1] is None:
+ first_index = index
return first_index, max_arr_ndim
elif type(arrays) is list and len(arrays) == 0:
# We've 'bottomed out' on an empty list
@@ -435,24 +451,17 @@ def _block(arrays, max_depth, result_ndim):
# ones to `a.shape` as necessary
return array(a, ndmin=ndim, copy=False, subok=True)
- def block_recursion(arrays, depth=0):
+ @recursive
+ def block_recursion(self, arrays, depth=0):
if depth < max_depth:
- if len(arrays) == 0:
- raise ValueError('Lists cannot be empty')
- arrs = [block_recursion(arr, depth+1) for arr in arrays]
+ arrs = [self(arr, depth+1) for arr in arrays]
return _nx.concatenate(arrs, axis=-(max_depth-depth))
else:
# We've 'bottomed out' - arrays is either a scalar or an array
# type(arrays) is not list
return atleast_nd(arrays, result_ndim)
- try:
- return block_recursion(arrays)
- finally:
- # recursive closures have a cyclic reference to themselves, which
- # requires gc to collect (gh-10620). To avoid this problem, for
- # performance and PyPy friendliness, we break the cycle:
- block_recursion = None
+ return block_recursion(arrays)
def block(arrays):
@@ -605,4 +614,10 @@ def block(arrays):
"""
bottom_index, arr_ndim = _block_check_depths_match(arrays)
list_ndim = len(bottom_index)
+ if bottom_index and bottom_index[-1] is None:
+ raise ValueError(
+ 'List at {} cannot be empty'.format(
+ _block_format_index(bottom_index)
+ )
+ )
return _block(arrays, list_ndim, max(arr_ndim, list_ndim))
diff --git a/numpy/core/src/multiarray/alloc.c b/numpy/core/src/multiarray/alloc.c
index ae4b81cf5..fe957bf16 100644
--- a/numpy/core/src/multiarray/alloc.c
+++ b/numpy/core/src/multiarray/alloc.c
@@ -35,6 +35,13 @@ typedef struct {
static cache_bucket datacache[NBUCKETS];
static cache_bucket dimcache[NBUCKETS_DIM];
+/* as the cache is managed in global variables verify the GIL is held */
+#if defined(NPY_PY3K)
+#define NPY_CHECK_GIL_HELD() PyGILState_Check()
+#else
+#define NPY_CHECK_GIL_HELD() 1
+#endif
+
/*
* very simplistic small memory block cache to avoid more expensive libc
* allocations
@@ -47,6 +54,7 @@ _npy_alloc_cache(npy_uintp nelem, npy_uintp esz, npy_uint msz,
{
assert((esz == 1 && cache == datacache) ||
(esz == sizeof(npy_intp) && cache == dimcache));
+ assert(NPY_CHECK_GIL_HELD());
if (nelem < msz) {
if (cache[nelem].available > 0) {
return cache[nelem].ptrs[--(cache[nelem].available)];
@@ -75,6 +83,7 @@ static NPY_INLINE void
_npy_free_cache(void * p, npy_uintp nelem, npy_uint msz,
cache_bucket * cache, void (*dealloc)(void *))
{
+ assert(NPY_CHECK_GIL_HELD());
if (p != NULL && nelem < msz) {
if (cache[nelem].available < NCACHE) {
cache[nelem].ptrs[cache[nelem].available++] = p;
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 0d79f294c..3df764a48 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -149,11 +149,6 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype,
{
PyArray_DatetimeMetaData *meta;
int flex_type_num;
- PyArrayObject *arr = NULL;
- PyArray_Descr *dtype = NULL;
- int ndim = 0;
- npy_intp dims[NPY_MAXDIMS];
- int result;
if (*flex_dtype == NULL) {
if (!PyErr_Occurred()) {
@@ -168,7 +163,7 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype,
/* Flexible types with expandable size */
if (PyDataType_ISUNSIZED(*flex_dtype)) {
- /* First replace the flex dtype */
+ /* First replace the flex_dtype */
PyArray_DESCR_REPLACE(*flex_dtype);
if (*flex_dtype == NULL) {
return;
@@ -259,6 +254,11 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype,
* GetArrayParamsFromObject won't iterate over
* array.
*/
+ PyArray_Descr *dtype = NULL;
+ PyArrayObject *arr = NULL;
+ int result;
+ int ndim = 0;
+ npy_intp dims[NPY_MAXDIMS];
list = PyArray_ToList((PyArrayObject *)data_obj);
result = PyArray_GetArrayParamsFromObject(
list,
@@ -273,6 +273,8 @@ PyArray_AdaptFlexibleDType(PyObject *data_obj, PyArray_Descr *data_dtype,
size = dtype->elsize;
}
}
+ Py_XDECREF(dtype);
+ Py_XDECREF(arr);
Py_DECREF(list);
}
else if (PyArray_IsPythonScalar(data_obj)) {
diff --git a/numpy/core/src/multiarray/item_selection.c b/numpy/core/src/multiarray/item_selection.c
index 141b2d922..925585704 100644
--- a/numpy/core/src/multiarray/item_selection.c
+++ b/numpy/core/src/multiarray/item_selection.c
@@ -833,8 +833,6 @@ _new_sortlike(PyArrayObject *op, int axis, PyArray_SortFunc *sort,
}
size = it->size;
- NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(op));
-
if (needcopy) {
buffer = npy_alloc_cache(N * elsize);
if (buffer == NULL) {
@@ -843,6 +841,8 @@ _new_sortlike(PyArrayObject *op, int axis, PyArray_SortFunc *sort,
}
}
+ NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(op));
+
while (size--) {
char *bufptr = it->dataptr;
@@ -917,8 +917,8 @@ _new_sortlike(PyArrayObject *op, int axis, PyArray_SortFunc *sort,
}
fail:
- npy_free_cache(buffer, N * elsize);
NPY_END_THREADS_DESCR(PyArray_DESCR(op));
+ npy_free_cache(buffer, N * elsize);
if (ret < 0 && !PyErr_Occurred()) {
/* Out of memory during sorting or buffer creation */
PyErr_NoMemory();
@@ -979,8 +979,6 @@ _new_argsortlike(PyArrayObject *op, int axis, PyArray_ArgSortFunc *argsort,
}
size = it->size;
- NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(op));
-
if (needcopy) {
valbuffer = npy_alloc_cache(N * elsize);
if (valbuffer == NULL) {
@@ -997,6 +995,8 @@ _new_argsortlike(PyArrayObject *op, int axis, PyArray_ArgSortFunc *argsort,
}
}
+ NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(op));
+
while (size--) {
char *valptr = it->dataptr;
npy_intp *idxptr = (npy_intp *)rit->dataptr;
@@ -1080,9 +1080,9 @@ _new_argsortlike(PyArrayObject *op, int axis, PyArray_ArgSortFunc *argsort,
}
fail:
+ NPY_END_THREADS_DESCR(PyArray_DESCR(op));
npy_free_cache(valbuffer, N * elsize);
npy_free_cache(idxbuffer, N * sizeof(npy_intp));
- NPY_END_THREADS_DESCR(PyArray_DESCR(op));
if (ret < 0) {
if (!PyErr_Occurred()) {
/* Out of memory during sorting or buffer creation */
@@ -1498,13 +1498,13 @@ PyArray_LexSort(PyObject *sort_keys, int axis)
char *valbuffer, *indbuffer;
int *swaps;
- valbuffer = npy_alloc_cache(N * maxelsize);
+ valbuffer = PyDataMem_NEW(N * maxelsize);
if (valbuffer == NULL) {
goto fail;
}
- indbuffer = npy_alloc_cache(N * sizeof(npy_intp));
+ indbuffer = PyDataMem_NEW(N * sizeof(npy_intp));
if (indbuffer == NULL) {
- npy_free_cache(indbuffer, N * sizeof(npy_intp));
+ PyDataMem_FREE(indbuffer);
goto fail;
}
swaps = malloc(n*sizeof(int));
@@ -1547,8 +1547,8 @@ PyArray_LexSort(PyObject *sort_keys, int axis)
sizeof(npy_intp), N, sizeof(npy_intp));
PyArray_ITER_NEXT(rit);
}
- npy_free_cache(valbuffer, N * maxelsize);
- npy_free_cache(indbuffer, N * sizeof(npy_intp));
+ PyDataMem_FREE(valbuffer);
+ PyDataMem_FREE(indbuffer);
free(swaps);
}
else {
diff --git a/numpy/distutils/command/config.py b/numpy/distutils/command/config.py
index 74a05e6a8..6b904d6ef 100644
--- a/numpy/distutils/command/config.py
+++ b/numpy/distutils/command/config.py
@@ -7,6 +7,7 @@ from __future__ import division, absolute_import, print_function
import os, signal
import warnings
import sys
+import subprocess
from distutils.command.config import config as old_config
from distutils.command.config import LANG_EXT
@@ -14,7 +15,7 @@ from distutils import log
from distutils.file_util import copy_file
from distutils.ccompiler import CompileError, LinkError
import distutils
-from numpy.distutils.exec_command import exec_command
+from numpy.distutils.exec_command import filepath_from_subprocess_output
from numpy.distutils.mingw32ccompiler import generate_manifest
from numpy.distutils.command.autodist import (check_gcc_function_attribute,
check_gcc_variable_attribute,
@@ -121,9 +122,13 @@ Original exception was: %s, and the Compiler class was %s
# correct path when compiling in Cygwin but with
# normal Win Python
if d.startswith('/usr/lib'):
- s, o = exec_command(['cygpath', '-w', d],
- use_tee=False)
- if not s: d = o
+ try:
+ d = subprocess.check_output(['cygpath',
+ '-w', d])
+ except (OSError, subprocess.CalledProcessError):
+ pass
+ else:
+ d = filepath_from_subprocess_output(d)
library_dirs.append(d)
for libname in self.fcompiler.libraries or []:
if libname not in libraries:
@@ -449,8 +454,24 @@ int main (void)
grabber.restore()
raise
exe = os.path.join('.', exe)
- exitstatus, output = exec_command(exe, execute_in='.',
- use_tee=use_tee)
+ try:
+ # specify cwd arg for consistency with
+ # historic usage pattern of exec_command()
+ # also, note that exe appears to be a string,
+ # which exec_command() handled, but we now
+ # use a list for check_output() -- this assumes
+ # that exe is always a single command
+ output = subprocess.check_output([exe], cwd='.')
+ except subprocess.CalledProcessError as exc:
+ exitstatus = exc.returncode
+ output = ''
+ except OSError:
+ # preserve the EnvironmentError exit status
+ # used historically in exec_command()
+ exitstatus = 127
+ output = ''
+ else:
+ output = filepath_from_subprocess_output(output)
if hasattr(os, 'WEXITSTATUS'):
exitcode = os.WEXITSTATUS(exitstatus)
if os.WIFSIGNALED(exitstatus):
diff --git a/numpy/distutils/fcompiler/ibm.py b/numpy/distutils/fcompiler/ibm.py
index d0c2202d4..c4cb2fca7 100644
--- a/numpy/distutils/fcompiler/ibm.py
+++ b/numpy/distutils/fcompiler/ibm.py
@@ -3,9 +3,10 @@ from __future__ import division, absolute_import, print_function
import os
import re
import sys
+import subprocess
from numpy.distutils.fcompiler import FCompiler
-from numpy.distutils.exec_command import exec_command, find_executable
+from numpy.distutils.exec_command import find_executable
from numpy.distutils.misc_util import make_temp_file
from distutils import log
@@ -35,9 +36,13 @@ class IBMFCompiler(FCompiler):
lslpp = find_executable('lslpp')
xlf = find_executable('xlf')
if os.path.exists(xlf) and os.path.exists(lslpp):
- s, o = exec_command(lslpp + ' -Lc xlfcmp')
- m = re.search(r'xlfcmp:(?P<version>\d+([.]\d+)+)', o)
- if m: version = m.group('version')
+ try:
+ o = subprocess.check_output([lslpp, '-Lc', 'xlfcmp'])
+ except (OSError, subprocess.CalledProcessError):
+ pass
+ else:
+ m = re.search(r'xlfcmp:(?P<version>\d+([.]\d+)+)', o)
+ if m: version = m.group('version')
xlf_dir = '/etc/opt/ibmcmp/xlf'
if version is None and os.path.isdir(xlf_dir):
diff --git a/numpy/f2py/__init__.py b/numpy/f2py/__init__.py
index fbb64f762..23a4b7c41 100644
--- a/numpy/f2py/__init__.py
+++ b/numpy/f2py/__init__.py
@@ -7,6 +7,10 @@ from __future__ import division, absolute_import, print_function
__all__ = ['run_main', 'compile', 'f2py_testing']
import sys
+import subprocess
+import os
+
+import numpy as np
from . import f2py2e
from . import f2py_testing
@@ -32,8 +36,12 @@ def compile(source,
Fortran source of module / subroutine to compile
modulename : str, optional
The name of the compiled python module
- extra_args : str, optional
+ extra_args : str or list, optional
Additional parameters passed to f2py
+
+ .. versionchanged:: 1.16.0
+ A list of args may also be provided.
+
verbose : bool, optional
Print f2py output to screen
source_fn : str, optional
@@ -48,25 +56,48 @@ def compile(source,
.. versionadded:: 1.11.0
"""
- from numpy.distutils.exec_command import exec_command
import tempfile
+ import shlex
+
if source_fn is None:
- f = tempfile.NamedTemporaryFile(suffix=extension)
+ f, fname = tempfile.mkstemp(suffix=extension)
+ # f is a file descriptor so need to close it
+ # carefully -- not with .close() directly
+ os.close(f)
else:
- f = open(source_fn, 'w')
+ fname = source_fn
try:
- f.write(source)
- f.flush()
+ with open(fname, 'w') as f:
+ f.write(str(source))
+
+ args = ['-c', '-m', modulename, f.name]
+
+ if isinstance(extra_args, np.compat.basestring):
+ is_posix = (os.name == 'posix')
+ extra_args = shlex.split(extra_args, posix=is_posix)
+
+ args.extend(extra_args)
- args = ' -c -m {} {} {}'.format(modulename, f.name, extra_args)
- c = '{} -c "import numpy.f2py as f2py2e;f2py2e.main()" {}'
- c = c.format(sys.executable, args)
- status, output = exec_command(c)
+ c = [sys.executable,
+ '-c',
+ 'import numpy.f2py as f2py2e;f2py2e.main()'] + args
+ try:
+ output = subprocess.check_output(c)
+ except subprocess.CalledProcessError as exc:
+ status = exc.returncode
+ output = ''
+ except OSError:
+ # preserve historic status code used by exec_command()
+ status = 127
+ output = ''
+ else:
+ status = 0
if verbose:
print(output)
finally:
- f.close()
+ if source_fn is None:
+ os.remove(fname)
return status
from numpy._pytesttester import PytestTester
diff --git a/numpy/f2py/tests/test_quoted_character.py b/numpy/f2py/tests/test_quoted_character.py
index 09fc7328f..c41be3a80 100644
--- a/numpy/f2py/tests/test_quoted_character.py
+++ b/numpy/f2py/tests/test_quoted_character.py
@@ -1,8 +1,13 @@
from __future__ import division, absolute_import, print_function
import sys
+import os
+import uuid
+from importlib import import_module
import pytest
+import numpy.f2py
+
from numpy.testing import assert_equal
from . import util
@@ -28,3 +33,108 @@ Cf2py intent(out) OUT1, OUT2, OUT3, OUT4, OUT5, OUT6
reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_quoted_character(self):
assert_equal(self.module.foo(), (b"'", b'"', b';', b'!', b'(', b')'))
+
+@pytest.mark.xfail(sys.version_info[0] < 3 and os.name == 'nt',
+ reason="our Appveyor CI configuration does not"
+ " have Fortran compilers available for"
+ " Python 2.x")
+@pytest.mark.parametrize("extra_args", [
+ # extra_args can be a list as of gh-11937
+ ['--noopt', '--debug'],
+ # test for string as well, using the same
+ # fcompiler options
+ '--noopt --debug',
+ # also test absence of extra_args
+ '',
+ ])
+def test_f2py_init_compile(extra_args):
+ # flush through the f2py __init__
+ # compile() function code path
+ # as a crude test for input handling
+ # following migration from exec_command()
+ # to subprocess.check_output() in gh-11937
+
+ # the Fortran 77 syntax requires 6 spaces
+ # before any commands, but more space may
+ # be added; gfortran can also compile
+ # with --ffree-form to remove the indentation
+ # requirement; here, the Fortran source is
+ # formatted to roughly match an example from
+ # the F2PY User Guide
+ fsource = '''
+ integer function foo()
+ foo = 10 + 5
+ return
+ end
+ '''
+ # use various helper functions in util.py to
+ # enable robust build / compile and
+ # reimport cycle in test suite
+ d = util.get_module_dir()
+ modulename = util.get_temp_module_name()
+
+ cwd = os.getcwd()
+ target = os.path.join(d, str(uuid.uuid4()) + '.f')
+ # try running compile() with and without a
+ # source_fn provided so that the code path
+ # where a temporary file for writing Fortran
+ # source is created is also explored
+ for source_fn in [target, None]:
+
+ # mimic the path changing behavior used
+ # by build_module() in util.py, but don't
+ # actually use build_module() because it
+ # has its own invocation of subprocess
+ # that circumvents the f2py.compile code
+ # block under test
+ try:
+ os.chdir(d)
+ ret_val = numpy.f2py.compile(fsource,
+ modulename=modulename,
+ extra_args=extra_args,
+ source_fn=source_fn)
+ finally:
+ os.chdir(cwd)
+
+ # check for compile success return value
+ assert_equal(ret_val, 0)
+
+ # we are not currently able to import the
+ # Python-Fortran interface module on Windows /
+ # Appveyor, even though we do get successful
+ # compilation on that platform with Python 3.x
+ if os.name != 'nt':
+ # check for sensible result of Fortran function;
+ # that means we can import the module name in Python
+ # and retrieve the result of the sum operation
+ return_check = import_module(modulename)
+ calc_result = return_check.foo()
+ assert_equal(calc_result, 15)
+
+def test_f2py_init_compile_failure():
+ # verify an appropriate integer status
+ # value returned by f2py.compile() when
+ # invalid Fortran is provided
+ ret_val = numpy.f2py.compile(b"invalid")
+ assert_equal(ret_val, 1)
+
+def test_f2py_init_compile_bad_cmd():
+ # verify that usage of invalid command in
+ # f2py.compile() returns status value of 127
+ # for historic consistency with exec_command()
+ # error handling
+
+ # patch the sys Python exe path temporarily to
+ # induce an OSError downstream
+ # NOTE: how bad of an idea is this patching?
+ try:
+ temp = sys.executable
+ sys.executable = 'does not exist'
+
+ # the OSError should take precedence over the invalid
+ # Fortran
+ ret_val = numpy.f2py.compile(b"invalid")
+
+ assert_equal(ret_val, 127)
+ finally:
+ sys.executable = temp
diff --git a/numpy/lib/npyio.py b/numpy/lib/npyio.py
index d8cfbf769..9a7b244ac 100644
--- a/numpy/lib/npyio.py
+++ b/numpy/lib/npyio.py
@@ -13,6 +13,7 @@ import numpy as np
from . import format
from ._datasource import DataSource
from numpy.core.multiarray import packbits, unpackbits
+from numpy.core._internal import recursive
from ._iotools import (
LineSplitter, NameValidator, StringConverter, ConverterError,
ConverterLockError, ConversionWarning, _is_string_like,
@@ -944,7 +945,8 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None,
fencoding = locale.getpreferredencoding()
# not to be confused with the flatten_dtype we import...
- def flatten_dtype_internal(dt):
+ @recursive
+ def flatten_dtype_internal(self, dt):
"""Unpack a structured data-type, and produce re-packing info."""
if dt.names is None:
# If the dtype is flattened, return.
@@ -964,7 +966,7 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None,
packing = []
for field in dt.names:
tp, bytes = dt.fields[field]
- flat_dt, flat_packing = flatten_dtype_internal(tp)
+ flat_dt, flat_packing = self(tp)
types.extend(flat_dt)
# Avoid extra nesting for subarrays
if tp.ndim > 0:
@@ -973,7 +975,8 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None,
packing.append((len(flat_dt), flat_packing))
return (types, packing)
- def pack_items(items, packing):
+ @recursive
+ def pack_items(self, items, packing):
"""Pack items into nested lists based on re-packing info."""
if packing is None:
return items[0]
@@ -985,7 +988,7 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None,
start = 0
ret = []
for length, subpacking in packing:
- ret.append(pack_items(items[start:start+length], subpacking))
+ ret.append(self(items[start:start+length], subpacking))
start += length
return tuple(ret)
@@ -1111,11 +1114,6 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None,
finally:
if fown:
fh.close()
- # recursive closures have a cyclic reference to themselves, which
- # requires gc to collect (gh-10620). To avoid this problem, for
- # performance and PyPy friendliness, we break the cycle:
- flatten_dtype_internal = None
- pack_items = None
if X is None:
X = np.array([], dtype)
diff --git a/numpy/lib/tests/test_arraypad.py b/numpy/lib/tests/test_arraypad.py
index 8ef9530a8..e62fccaa0 100644
--- a/numpy/lib/tests/test_arraypad.py
+++ b/numpy/lib/tests/test_arraypad.py
@@ -519,6 +519,21 @@ class TestConstant(object):
expected = np.full(7, int64_max, dtype=np.int64)
assert_array_equal(test, expected)
+ def test_check_object_array(self):
+ arr = np.empty(1, dtype=object)
+ obj_a = object()
+ arr[0] = obj_a
+ obj_b = object()
+ obj_c = object()
+ arr = np.pad(arr, pad_width=1, mode='constant',
+ constant_values=(obj_b, obj_c))
+
+ expected = np.empty((3,), dtype=object)
+ expected[0] = obj_b
+ expected[1] = obj_a
+ expected[2] = obj_c
+
+ assert_array_equal(arr, expected)
class TestLinearRamp(object):
def test_check_simple(self):
diff --git a/numpy/lib/tests/test_io.py b/numpy/lib/tests/test_io.py
index 711415c6a..d9fed13dc 100644
--- a/numpy/lib/tests/test_io.py
+++ b/numpy/lib/tests/test_io.py
@@ -2412,3 +2412,9 @@ def test_load_refcount():
with assert_no_gc_cycles():
np.load(f)
+
+ f.seek(0)
+ dt = [("a", 'u1', 2), ("b", 'u1', 2)]
+ with assert_no_gc_cycles():
+ x = np.loadtxt(TextIO("0 1 2 3"), dtype=dt)
+ assert_equal(x, np.array([((0, 1), (2, 3))], dtype=dt))
diff --git a/numpy/ma/core.py b/numpy/ma/core.py
index 65ce967ae..a6c3e64d6 100644
--- a/numpy/ma/core.py
+++ b/numpy/ma/core.py
@@ -46,6 +46,7 @@ from numpy.compat import (
from numpy import expand_dims
from numpy.core.multiarray import normalize_axis_index
from numpy.core.numeric import normalize_axis_tuple
+from numpy.core._internal import recursive
if sys.version_info[0] >= 3:
@@ -1729,12 +1730,13 @@ def mask_or(m1, m2, copy=False, shrink=True):
"""
- def _recursive_mask_or(m1, m2, newmask):
+ @recursive
+ def _recursive_mask_or(self, m1, m2, newmask):
names = m1.dtype.names
for name in names:
current1 = m1[name]
if current1.dtype.names is not None:
- _recursive_mask_or(current1, m2[name], newmask[name])
+ self(current1, m2[name], newmask[name])
else:
umath.logical_or(current1, m2[name], newmask[name])
return