From d7509eca24e194b6322f3d9bbd156ffc62e745ee Mon Sep 17 00:00:00 2001 From: kibitzing Date: Fri, 15 Aug 2025 17:16:36 +0900 Subject: [PATCH 01/10] ENH: add ndmax parameter to np.array --- numpy/_core/src/multiarray/ctors.c | 8 +++-- numpy/_core/src/multiarray/multiarraymodule.c | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/numpy/_core/src/multiarray/ctors.c b/numpy/_core/src/multiarray/ctors.c index 498fa78118b3..e49bc2a5422b 100644 --- a/numpy/_core/src/multiarray/ctors.c +++ b/numpy/_core/src/multiarray/ctors.c @@ -1545,6 +1545,10 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr, int ndim = 0; npy_intp dims[NPY_MAXDIMS]; + if (max_depth == 0 || max_depth > NPY_MAXDIMS) { + max_depth = NPY_MAXDIMS; + } + if (context != NULL) { PyErr_SetString(PyExc_RuntimeError, "'context' must be NULL"); return NULL; @@ -1563,7 +1567,7 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr, Py_BEGIN_CRITICAL_SECTION(op); ndim = PyArray_DiscoverDTypeAndShape( - op, NPY_MAXDIMS, dims, &cache, in_DType, in_descr, &dtype, + op, max_depth, dims, &cache, in_DType, in_descr, &dtype, copy, &was_copied_by__array__); if (ndim < 0) { @@ -1583,7 +1587,7 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr, npy_free_coercion_cache(cache); goto cleanup; } - if (max_depth != 0 && ndim > max_depth) { + if (ndim > max_depth && (in_DType == NULL || in_DType->type_num != NPY_OBJECT)) { PyErr_SetString(PyExc_ValueError, "object too deep for desired array"); npy_free_coercion_cache(cache); diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index 3d82e6c7f448..d32c0808351d 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1560,7 +1560,7 @@ _prepend_ones(PyArrayObject *arr, int nd, int ndmin, NPY_ORDER order) static inline PyObject * _array_fromobject_generic( PyObject *op, PyArray_Descr *in_descr, PyArray_DTypeMeta *in_DType, - NPY_COPYMODE copy, NPY_ORDER order, npy_bool subok, int ndmin) + NPY_COPYMODE copy, NPY_ORDER order, npy_bool subok, int ndmin, int ndmax) { PyArrayObject *oparr = NULL, *ret = NULL; PyArray_Descr *oldtype = NULL; @@ -1570,10 +1570,9 @@ _array_fromobject_generic( Py_XINCREF(in_descr); PyArray_Descr *dtype = in_descr; - if (ndmin > NPY_MAXDIMS) { + if (ndmin > ndmax) { PyErr_Format(PyExc_ValueError, - "ndmin bigger than allowable number of dimensions " - "NPY_MAXDIMS (=%d)", NPY_MAXDIMS); + "ndmin must be <= ndmax (=%d)", ndmax); goto finish; } /* fast exit if simple call */ @@ -1682,7 +1681,7 @@ _array_fromobject_generic( flags |= NPY_ARRAY_FORCECAST; ret = (PyArrayObject *)PyArray_CheckFromAny_int( - op, dtype, in_DType, 0, 0, flags, NULL); + op, dtype, in_DType, 0, ndmax, flags, NULL); finish: Py_XDECREF(dtype); @@ -1713,6 +1712,7 @@ array_array(PyObject *NPY_UNUSED(ignored), npy_bool subok = NPY_FALSE; NPY_COPYMODE copy = NPY_COPY_ALWAYS; int ndmin = 0; + int ndmax = NPY_MAXDIMS; npy_dtype_info dt_info = {NULL, NULL}; NPY_ORDER order = NPY_KEEPORDER; PyObject *like = Py_None; @@ -1726,6 +1726,7 @@ array_array(PyObject *NPY_UNUSED(ignored), "$order", &PyArray_OrderConverter, &order, "$subok", &PyArray_BoolConverter, &subok, "$ndmin", &PyArray_PythonPyIntFromInt, &ndmin, + "$ndmax", &PyArray_PythonPyIntFromInt, &ndmax, "$like", NULL, &like, NULL, NULL, NULL) < 0) { Py_XDECREF(dt_info.descr); @@ -1747,8 +1748,17 @@ array_array(PyObject *NPY_UNUSED(ignored), op = args[0]; } + if (ndmax > NPY_MAXDIMS) { + PyErr_Format(PyExc_ValueError, + "ndmax bigger than allowable number of dimensions " + "NPY_MAXDIMS (=%d)", NPY_MAXDIMS); + Py_XDECREF(dt_info.descr); + Py_XDECREF(dt_info.dtype); + return NULL; + } + PyObject *res = _array_fromobject_generic( - op, dt_info.descr, dt_info.dtype, copy, order, subok, ndmin); + op, dt_info.descr, dt_info.dtype, copy, order, subok, ndmin, ndmax); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return res; @@ -1794,7 +1804,7 @@ array_asarray(PyObject *NPY_UNUSED(ignored), } PyObject *res = _array_fromobject_generic( - op, dt_info.descr, dt_info.dtype, copy, order, NPY_FALSE, 0); + op, dt_info.descr, dt_info.dtype, copy, order, NPY_FALSE, 0, NPY_MAXDIMS); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return res; @@ -1840,7 +1850,7 @@ array_asanyarray(PyObject *NPY_UNUSED(ignored), } PyObject *res = _array_fromobject_generic( - op, dt_info.descr, dt_info.dtype, copy, order, NPY_TRUE, 0); + op, dt_info.descr, dt_info.dtype, copy, order, NPY_TRUE, 0, NPY_MAXDIMS); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return res; @@ -1882,7 +1892,7 @@ array_ascontiguousarray(PyObject *NPY_UNUSED(ignored), PyObject *res = _array_fromobject_generic( op, dt_info.descr, dt_info.dtype, NPY_COPY_IF_NEEDED, NPY_CORDER, NPY_FALSE, - 1); + 1, NPY_MAXDIMS); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return res; @@ -1924,7 +1934,7 @@ array_asfortranarray(PyObject *NPY_UNUSED(ignored), PyObject *res = _array_fromobject_generic( op, dt_info.descr, dt_info.dtype, NPY_COPY_IF_NEEDED, NPY_FORTRANORDER, - NPY_FALSE, 1); + NPY_FALSE, 1, NPY_MAXDIMS); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return res; From 4f698ca703d6b19d53f9d3dafc5de2b63e7120a3 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Fri, 15 Aug 2025 19:09:08 +0900 Subject: [PATCH 02/10] ENH: validate ndmax argument is positive --- numpy/_core/src/multiarray/multiarraymodule.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index d32c0808351d..18553005e9a0 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1748,10 +1748,12 @@ array_array(PyObject *NPY_UNUSED(ignored), op = args[0]; } - if (ndmax > NPY_MAXDIMS) { - PyErr_Format(PyExc_ValueError, - "ndmax bigger than allowable number of dimensions " - "NPY_MAXDIMS (=%d)", NPY_MAXDIMS); + if (ndmax > NPY_MAXDIMS || ndmax <= 0) { + if (ndmax > NPY_MAXDIMS) { + PyErr_Format(PyExc_ValueError, "ndmax must be <= NPY_MAXDIMS (=%d)", NPY_MAXDIMS); + } else { + PyErr_Format(PyExc_ValueError, "ndmax must be > 0"); + } Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return NULL; From fe974b9e2b8db7c7347cef89ab4c1b6a05020f8f Mon Sep 17 00:00:00 2001 From: kibitzing Date: Fri, 15 Aug 2025 19:12:29 +0900 Subject: [PATCH 03/10] TST: add ndmax tests for array creation --- numpy/_core/tests/test_multiarray.py | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/numpy/_core/tests/test_multiarray.py b/numpy/_core/tests/test_multiarray.py index cf2f899b7991..764b1bc6011b 100644 --- a/numpy/_core/tests/test_multiarray.py +++ b/numpy/_core/tests/test_multiarray.py @@ -1279,6 +1279,65 @@ def test_creation_from_dtypemeta(self, func): assert_array_equal(arr1, arr2) assert arr2.dtype == dtype + def test_ndmax_less_than_actual_dims_dtype_object(self): + data = [[1, 2, 3], [4, 5, 6]] + arr = np.array(data, ndmax=1, dtype=object) + assert arr.ndim == 1 + assert arr.shape == (2,) + assert arr.dtype == object + + data = [[1, 2, 3], [4, 5]] + arr = np.array(data, ndmax=1, dtype=object) + assert arr.ndim == 1 + assert arr.shape == (2,) + assert arr.dtype == object + + data = [[[1], [2]], [[3], [4]]] + arr = np.array(data, ndmax=2, dtype=object) + assert arr.ndim == 2 + assert arr.shape == (2, 2) + assert arr.dtype == object + + def test_ndmax_equal_to_actual_dims(self): + data = [[1, 2], [3, 4]] + arr = np.array(data, ndmax=2) + assert arr.ndim == 2 + assert_array_equal(arr, np.array(data)) + + def test_ndmax_greater_than_actual_dims(self): + data = [[1, 2], [3, 4]] + arr = np.array(data, ndmax=3) + assert arr.ndim == 2 + assert_array_equal(arr, np.array(data)) + + def test_ndmax_less_than_actual_dims(self): + data = [[[1], [2]], [[3], [4]]] + with pytest.raises(ValueError, + match="setting an array element with a sequence. " + "The requested array would exceed the maximum number of dimension of 2."): + np.array(data, ndmax=2) + + def test_ndmax_less_than_ndmin(self): + data = [[[1], [2]], [[3], [4]]] + with pytest.raises(ValueError, match="ndmin must be <= ndmax"): + np.array(data, ndmax=1, ndmin=2) + + def test_ndmax_is_negative(self): + data = [1, 2, 3] + with pytest.raises(ValueError, match="ndmax must be > 0"): + np.array(data, ndmax=-1) + + def test_ndmax_is_zero(self): + data = [1, 2, 3] + with pytest.raises(ValueError, match="ndmax must be > 0"): + np.array(data, ndmax=0) + + def test_ndmax_greather_than_NPY_MAXDIMS(self): + data = [1, 2, 3] + # current NPY_MAXDIMS is 64 + with pytest.raises(ValueError, match="ndmax must be <= NPY_MAXDIMS"): + np.array(data, ndmax=65) + class TestStructured: def test_subarray_field_access(self): From ecaaee2999633f0b1e4ee1d9096b5bc676c6feee Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 02:26:15 +0900 Subject: [PATCH 04/10] ENH: allow np.array with ndmax=0 to create 0-D array --- numpy/_core/src/multiarray/array_converter.c | 2 +- numpy/_core/src/multiarray/ctors.c | 12 ++++++--- numpy/_core/src/multiarray/multiarraymodule.c | 4 +-- numpy/_core/src/multiarray/scalartypes.c.src | 2 +- numpy/_core/tests/test_multiarray.py | 26 ++++++++++++++----- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/numpy/_core/src/multiarray/array_converter.c b/numpy/_core/src/multiarray/array_converter.c index 496173038954..10dc83ac657f 100644 --- a/numpy/_core/src/multiarray/array_converter.c +++ b/numpy/_core/src/multiarray/array_converter.c @@ -83,7 +83,7 @@ array_converter_new( } else { item->array = (PyArrayObject *)PyArray_FromAny_int( - item->object, NULL, NULL, 0, 0, 0, NULL, + item->object, NULL, NULL, 0, NPY_MAXDIMS, 0, NULL, &item->scalar_input); if (item->array == NULL) { goto fail; diff --git a/numpy/_core/src/multiarray/ctors.c b/numpy/_core/src/multiarray/ctors.c index e49bc2a5422b..fd351bdba0f8 100644 --- a/numpy/_core/src/multiarray/ctors.c +++ b/numpy/_core/src/multiarray/ctors.c @@ -1508,6 +1508,10 @@ PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth, return NULL; } + if (max_depth == 0 || max_depth > NPY_MAXDIMS) { + max_depth = NPY_MAXDIMS; + } + int was_scalar; PyObject* ret = PyArray_FromAny_int( op, dt_info.descr, dt_info.dtype, @@ -1545,10 +1549,6 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr, int ndim = 0; npy_intp dims[NPY_MAXDIMS]; - if (max_depth == 0 || max_depth > NPY_MAXDIMS) { - max_depth = NPY_MAXDIMS; - } - if (context != NULL) { PyErr_SetString(PyExc_RuntimeError, "'context' must be NULL"); return NULL; @@ -1802,6 +1802,10 @@ PyArray_CheckFromAny(PyObject *op, PyArray_Descr *descr, int min_depth, return NULL; } + if (max_depth == 0 || max_depth > NPY_MAXDIMS) { + max_depth = NPY_MAXDIMS; + } + PyObject* ret = PyArray_CheckFromAny_int( op, dt_info.descr, dt_info.dtype, min_depth, max_depth, requires, context); diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index 18553005e9a0..fc53041fca6e 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1748,11 +1748,11 @@ array_array(PyObject *NPY_UNUSED(ignored), op = args[0]; } - if (ndmax > NPY_MAXDIMS || ndmax <= 0) { + if (ndmax > NPY_MAXDIMS || ndmax < 0) { if (ndmax > NPY_MAXDIMS) { PyErr_Format(PyExc_ValueError, "ndmax must be <= NPY_MAXDIMS (=%d)", NPY_MAXDIMS); } else { - PyErr_Format(PyExc_ValueError, "ndmax must be > 0"); + PyErr_Format(PyExc_ValueError, "ndmax must be >= 0"); } Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); diff --git a/numpy/_core/src/multiarray/scalartypes.c.src b/numpy/_core/src/multiarray/scalartypes.c.src index 5e3a3ba71d3e..a6170936a5f3 100644 --- a/numpy/_core/src/multiarray/scalartypes.c.src +++ b/numpy/_core/src/multiarray/scalartypes.c.src @@ -226,7 +226,7 @@ find_binary_operation_path( */ int was_scalar; PyArrayObject *arr = (PyArrayObject *)PyArray_FromAny_int( - other, NULL, NULL, 0, 0, 0, NULL, &was_scalar); + other, NULL, NULL, 0, NPY_MAXDIMS, 0, NULL, &was_scalar); if (arr == NULL) { return -1; } diff --git a/numpy/_core/tests/test_multiarray.py b/numpy/_core/tests/test_multiarray.py index 764b1bc6011b..8017c4c9b2c0 100644 --- a/numpy/_core/tests/test_multiarray.py +++ b/numpy/_core/tests/test_multiarray.py @@ -1317,6 +1317,25 @@ def test_ndmax_less_than_actual_dims(self): "The requested array would exceed the maximum number of dimension of 2."): np.array(data, ndmax=2) + def test_ndmax_is_zero(self): + data = [1, 2, 3] + arr = np.array(data, ndmax=0, dtype=object) + assert arr.ndim == 0 + assert arr.shape == () + assert arr.dtype == object + + data = [[1, 2, 3], [4, 5, 6]] + arr = np.array(data, ndmax=0, dtype=object) + assert arr.ndim == 0 + assert arr.shape == () + assert arr.dtype == object + + data = [[1, 2, 3], [4, 5]] + arr = np.array(data, ndmax=0, dtype=object) + assert arr.ndim == 0 + assert arr.shape == () + assert arr.dtype == object + def test_ndmax_less_than_ndmin(self): data = [[[1], [2]], [[3], [4]]] with pytest.raises(ValueError, match="ndmin must be <= ndmax"): @@ -1324,14 +1343,9 @@ def test_ndmax_less_than_ndmin(self): def test_ndmax_is_negative(self): data = [1, 2, 3] - with pytest.raises(ValueError, match="ndmax must be > 0"): + with pytest.raises(ValueError, match="ndmax must be >= 0"): np.array(data, ndmax=-1) - def test_ndmax_is_zero(self): - data = [1, 2, 3] - with pytest.raises(ValueError, match="ndmax must be > 0"): - np.array(data, ndmax=0) - def test_ndmax_greather_than_NPY_MAXDIMS(self): data = [1, 2, 3] # current NPY_MAXDIMS is 64 From cfce2c24bfecef147291bf0dced18fefc4f7f968 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 12:20:18 +0900 Subject: [PATCH 05/10] MNT: simplify ndmax validation and error message --- numpy/_core/src/multiarray/multiarraymodule.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index fc53041fca6e..cb50a1d10df3 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1749,11 +1749,7 @@ array_array(PyObject *NPY_UNUSED(ignored), } if (ndmax > NPY_MAXDIMS || ndmax < 0) { - if (ndmax > NPY_MAXDIMS) { - PyErr_Format(PyExc_ValueError, "ndmax must be <= NPY_MAXDIMS (=%d)", NPY_MAXDIMS); - } else { - PyErr_Format(PyExc_ValueError, "ndmax must be >= 0"); - } + PyErr_Format(PyExc_ValueError, "ndmax must be in the range [0, NPY_MAXDIMS (%d)] ", NPY_MAXDIMS); Py_XDECREF(dt_info.descr); Py_XDECREF(dt_info.dtype); return NULL; From dabaeda1c20856ed7742eb06710c197c3bc5edd1 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 12:22:03 +0900 Subject: [PATCH 06/10] MNT: improve consistency in error message formatting --- numpy/_core/src/multiarray/multiarraymodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index cb50a1d10df3..a7fdf3efba17 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1572,7 +1572,7 @@ _array_fromobject_generic( if (ndmin > ndmax) { PyErr_Format(PyExc_ValueError, - "ndmin must be <= ndmax (=%d)", ndmax); + "ndmin must be <= ndmax (%d)", ndmax); goto finish; } /* fast exit if simple call */ From 0f1ca84c71d922efe04781bc86d0a9e600972816 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 12:54:15 +0900 Subject: [PATCH 07/10] DOC: add comment explaining legacy behavior of max_depth=0 --- numpy/_core/src/multiarray/ctors.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/numpy/_core/src/multiarray/ctors.c b/numpy/_core/src/multiarray/ctors.c index fd351bdba0f8..38da6f314848 100644 --- a/numpy/_core/src/multiarray/ctors.c +++ b/numpy/_core/src/multiarray/ctors.c @@ -1508,6 +1508,12 @@ PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth, return NULL; } + /* + * The internal implementation treats 0 as actually wanting a zero-dimensional + * array, but the API for this function has typically treated it as + * "anything is fine", so convert here. + * TODO: should we use another value as a placeholder instead? + */ if (max_depth == 0 || max_depth > NPY_MAXDIMS) { max_depth = NPY_MAXDIMS; } @@ -1802,6 +1808,7 @@ PyArray_CheckFromAny(PyObject *op, PyArray_Descr *descr, int min_depth, return NULL; } + /* See comment in PyArray_FromAny for rationale */ if (max_depth == 0 || max_depth > NPY_MAXDIMS) { max_depth = NPY_MAXDIMS; } From 89f36efcd6a1b50ac68a7745ab37d24eea173604 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 14:45:55 +0900 Subject: [PATCH 08/10] TST: update tests to reflect new ndmax validation and error message --- numpy/_core/tests/test_multiarray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpy/_core/tests/test_multiarray.py b/numpy/_core/tests/test_multiarray.py index 8017c4c9b2c0..da4eeb91cfc2 100644 --- a/numpy/_core/tests/test_multiarray.py +++ b/numpy/_core/tests/test_multiarray.py @@ -1343,13 +1343,13 @@ def test_ndmax_less_than_ndmin(self): def test_ndmax_is_negative(self): data = [1, 2, 3] - with pytest.raises(ValueError, match="ndmax must be >= 0"): + with pytest.raises(ValueError, match="ndmax must be in the range"): np.array(data, ndmax=-1) def test_ndmax_greather_than_NPY_MAXDIMS(self): data = [1, 2, 3] # current NPY_MAXDIMS is 64 - with pytest.raises(ValueError, match="ndmax must be <= NPY_MAXDIMS"): + with pytest.raises(ValueError, match="ndmax must be in the range"): np.array(data, ndmax=65) From 9ae4f19e0af879017f248fe066dc9d20d97c2a21 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 17:29:59 +0900 Subject: [PATCH 09/10] DOC: add documentation and examples for np.array ndmax parameter --- numpy/_core/_add_newdocs.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/numpy/_core/_add_newdocs.py b/numpy/_core/_add_newdocs.py index ed8cf50ee360..e3009a490bd3 100644 --- a/numpy/_core/_add_newdocs.py +++ b/numpy/_core/_add_newdocs.py @@ -806,7 +806,7 @@ add_newdoc('numpy._core.multiarray', 'array', """ array(object, dtype=None, *, copy=True, order='K', subok=False, ndmin=0, - like=None) + ndmax=None, like=None) Create an array. @@ -855,6 +855,15 @@ Specifies the minimum number of dimensions that the resulting array should have. Ones will be prepended to the shape as needed to meet this requirement. + ndmax : int, optional + Specifies the maximum number of dimensions to create when inferring + shape from nested sequences. By default, NumPy recurses through all + nesting levels (up to the compile-time constant ``NPY_MAXDIMS``). + Setting ``ndmax`` stops recursion at the specified depth, preserving + deeper nested structures as objects instead of promoting them to + higher-dimensional arrays. In this case, ``dtype=object`` is required. + + .. versionadded:: 2.4.0 ${ARRAY_FUNCTION_LIKE} .. versionadded:: 1.20.0 @@ -926,6 +935,21 @@ matrix([[1, 2], [3, 4]]) + Limiting the maximum dimensions with ``ndmax``: + + >>> a = np.array([[1, 2], [3, 4]], dtype=object, ndmax=2) + >>> a + array([[1, 2], + [3, 4]], dtype=object) + >>> a.shape + (2, 2) + + >>> b = np.array([[1, 2], [3, 4]], dtype=object, ndmax=1) + >>> b + array([list([1, 2]), list([3, 4])], dtype=object) + >>> b.shape + (2,) + """) add_newdoc('numpy._core.multiarray', 'asarray', From 8ba91fcf302e0a6dde265d1aa0e194f9738833e8 Mon Sep 17 00:00:00 2001 From: kibitzing Date: Sat, 16 Aug 2025 18:49:06 +0900 Subject: [PATCH 10/10] DOC: add release note for numpy.array ndmax parameter --- .../upcoming_changes/29569.new_feature.rst | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 doc/release/upcoming_changes/29569.new_feature.rst diff --git a/doc/release/upcoming_changes/29569.new_feature.rst b/doc/release/upcoming_changes/29569.new_feature.rst new file mode 100644 index 000000000000..ac014c07c7a0 --- /dev/null +++ b/doc/release/upcoming_changes/29569.new_feature.rst @@ -0,0 +1,27 @@ +``ndmax`` option for `numpy.array` +---------------------------------------------------- +The ``ndmax`` option is now available for `numpy.array`. +It explicitly limits the maximum number of dimensions created from nested sequences. + +This is particularly useful when creating arrays of list-like objects with ``dtype=object``. +By default, NumPy recurses through all nesting levels to create the highest possible +dimensional array, but this behavior may not be desired when the intent is to preserve +nested structures as objects. The ``ndmax`` parameter provides explicit control over +this recursion depth. + +.. code-block:: python + + # Default behavior: Creates a 2D array + >>> a = np.array([[1, 2], [3, 4]], dtype=object) + >>> a + array([[1, 2], + [3, 4]], dtype=object) + >>> a.shape + (2, 2) + + # With ndmax=1: Creates a 1D array + >>> b = np.array([[1, 2], [3, 4]], dtype=object, ndmax=1) + >>> b + array([list([1, 2]), list([3, 4])], dtype=object) + >>> b.shape + (2,)