diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 0c8274756b8c..db980e27f2b6 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -23,7 +23,7 @@ jobs: # To enable this workflow on a fork, comment out: if: github.repository == 'numpy/numpy' env: - PYODIDE_VERSION: 0.25.0 + PYODIDE_VERSION: 0.25.1 # PYTHON_VERSION and EMSCRIPTEN_VERSION are determined by PYODIDE_VERSION. # The appropriate versions can be found in the Pyodide repodata.json # "info" field, or in Makefile.envs: @@ -69,11 +69,6 @@ jobs: with open(env_file, "a") as myfile: myfile.write(f"PYODIDE_BUILD_PATH={pyodide_build_path}\n") - - name: Apply patch(es) for pyodide-build installation - run: | - ls -a ${{ env.PYODIDE_BUILD_PATH }} - patch -d "${{ env.PYODIDE_BUILD_PATH }}" -p1 < tools/ci/emscripten/0001-do-not-set-meson-environment-variable-pyodide-gh-4502.patch - - name: Build NumPy for Pyodide run: | pyodide build -Cbuild-dir=build -Csetup-args="--cross-file=$PWD/tools/ci/emscripten/emscripten.meson.cross" -Csetup-args="-Dblas=none" -Csetup-args="-Dlapack=none" diff --git a/doc/source/numpy_2_0_migration_guide.rst b/doc/source/numpy_2_0_migration_guide.rst index 9ecbd90d6318..2b345942db21 100644 --- a/doc/source/numpy_2_0_migration_guide.rst +++ b/doc/source/numpy_2_0_migration_guide.rst @@ -425,3 +425,24 @@ The :ref:`copy keyword behavior changes ` in older NumPy versions as well. If ``copy`` keyword is considered in the ``__array__`` method implementation, then for ``copy=True`` always return a new copy. + + +Writing numpy-version-dependent code +------------------------------------ + +It should be fairly rare to have to write code that explicitly branches on the +``numpy`` version - in most cases, code can be rewritten to be compatible with +1.x and 2.0 at the same time. However, if it is necessary, here is a suggested +code pattern to use, using `numpy.lib.NumpyVersion`:: + + # example with AxisError, which is no longer available in + # the main namespace in 2.0, and not available in the + # `exceptions` namespace in <1.25.0 (example uses <2.0.0b1 + # for illustrative purposes): + if np.lib.NumpyVersion(np.__version__) >= '2.0.0b1': + from numpy.exceptions import AxisError + else: + from numpy import AxisError + +This pattern will work correctly including with NumPy release candidates, which +is important during the 2.0.0 release period. diff --git a/doc/source/reference/c-api/types-and-structures.rst b/doc/source/reference/c-api/types-and-structures.rst index 9c2b432ef726..b0f274f38a74 100644 --- a/doc/source/reference/c-api/types-and-structures.rst +++ b/doc/source/reference/c-api/types-and-structures.rst @@ -906,6 +906,30 @@ PyArray_DTypeMeta and PyArrayDTypeMeta_Spec of functions in the DType API. Slot IDs must be one of the DType slot IDs enumerated in :ref:`dtype-slots`. +Exposed DTypes classes (``PyArray_DTypeMeta`` objects) +------------------------------------------------------ + +For use with promoters, NumPy exposes a number of Dtypes following the +pattern ``PyArray_DType`` corresponding to those found in `np.dtypes`. + +Additionally, the three DTypes, ``PyArray_PyLongDType``, +``PyArray_PyFloatDType``, ``PyArray_PyComplexDType`` correspond to the +Python scalar values. These cannot be used in all places, but do allow +for example the common dtype operation and implementing promotion with them +may be necessary. + +Further, the following abstract DTypes are defined which cover both the +builtin NumPy ones and the python ones, and users can in principle subclass +from them (this does not inherit any DType specific functionality): +* ``PyArray_IntAbstractDType`` +* ``PyArray_FloatAbstractDType`` +* ``PyArray_ComplexAbstractDType`` + +.. warning:: + As of NumPy 2.0, the *only* valid use for these DTypes is registering a + promoter conveniently to e.g. match "any integers" (and subclass checks). + Because of this, they are not exposed to Python. + PyUFunc_Type and PyUFuncObject ------------------------------ diff --git a/doc/source/reference/module_structure.rst b/doc/source/reference/module_structure.rst index 2db9de7f03a8..01a5bcff7fbc 100644 --- a/doc/source/reference/module_structure.rst +++ b/doc/source/reference/module_structure.rst @@ -35,6 +35,7 @@ Special-purpose namespaces - :ref:`numpy.emath ` - mathematical functions with automatic domain - :ref:`numpy.lib ` - utilities & functionality which do not fit the main namespace - :ref:`numpy.rec ` - record arrays (largely superseded by dataframe libraries) +- :ref:`numpy.version ` - small module with more detailed version info Legacy namespaces ================= @@ -67,6 +68,7 @@ and/or this code is deprecated or isn't reliable. numpy.emath numpy.lib numpy.rec + numpy.version numpy.char numpy.distutils numpy.f2py <../f2py/index> diff --git a/doc/source/reference/routines.version.rst b/doc/source/reference/routines.version.rst new file mode 100644 index 000000000000..72c48a752cf6 --- /dev/null +++ b/doc/source/reference/routines.version.rst @@ -0,0 +1,38 @@ +.. currentmodule:: numpy.version + +.. _routines.version: + +******************* +Version information +******************* + +The ``numpy.version`` submodule includes several constants that expose more +detailed information about the exact version of the installed ``numpy`` +package: + +.. data:: version + + Version string for the installed package - matches ``numpy.__version__``. + +.. data:: full_version + + Version string - the same as ``numpy.version.version``. + +.. data:: short_version + + Version string without any local build identifiers. + + .. rubric:: Examples + + >>> np.__version__ + '2.1.0.dev0+git20240319.2ea7ce0' # may vary + >>> np.version.short_version + '2.1.0.dev0' # may vary + +.. data:: git_revision + + String containing the git hash of the commit from which ``numpy`` was built. + +.. data:: release + + ``True`` if this version is a ``numpy`` release, ``False`` if a dev version. diff --git a/doc/source/user/absolute_beginners.rst b/doc/source/user/absolute_beginners.rst index 05c379f4cd4f..f294f384f7c1 100644 --- a/doc/source/user/absolute_beginners.rst +++ b/doc/source/user/absolute_beginners.rst @@ -1525,7 +1525,7 @@ If you want to store a single ndarray object, store it as a .npy file using save it as a .npz file using ``np.savez``. You can also save several arrays into a single file in compressed npz format with `savez_compressed`. -It's easy to save and load and array with ``np.save()``. Just make sure to +It's easy to save and load an array with ``np.save()``. Just make sure to specify the array you want to save and a file name. For example, if you create this array:: diff --git a/doc/source/user/troubleshooting-importerror.rst b/doc/source/user/troubleshooting-importerror.rst index 232f9f7e2bf2..adbc9d898846 100644 --- a/doc/source/user/troubleshooting-importerror.rst +++ b/doc/source/user/troubleshooting-importerror.rst @@ -183,6 +183,34 @@ that usually works is to upgrade the NumPy version:: pip install numpy --upgrade + +Downstream ImportError or AttributeError +======================================== + +If you see a message such as:: + + A module that was compiled using NumPy 1.x cannot be run in + NumPy 2.0.0 as it may crash. To support both 1.x and 2.x + versions of NumPy, modules must be compiled with NumPy 2.0. + Some module may need to rebuild instead e.g. with 'pybind11>=2.12'. + +Either as an ``ImportError`` or with:: + + AttributeError: _ARRAY_API not found + +Then you are using NumPy 2 together with a module that was build with NumPy 1. +NumPy 2 made some changes that require rebuilding such modules to avoid +possibly incorrect results or crashes. + +As the error message suggests, the easiest solution is likely to downgrade +NumPy to `numpy<2`. +Alternatively, you can search the traceback (from the back) to find the first +line that isn't inside NumPy to see which module needs to be updated. + +NumPy 2 was released in the first half of 2024 and especially smaller +modules downstream are expected need time to adapt and publish a new version. + + Segfaults or crashes ==================== diff --git a/numpy/_build_utils/gitversion.py b/numpy/_build_utils/gitversion.py index 4ee6e00bbd65..defc704c41eb 100644 --- a/numpy/_build_utils/gitversion.py +++ b/numpy/_build_utils/gitversion.py @@ -70,6 +70,9 @@ def git_version(version): # For NumPy 2.0, this should only have one field: `version` template = textwrap.dedent(f''' + """ + Module to expose more detailed version info for the installed `numpy` + """ version = "{version}" __version__ = version full_version = version diff --git a/numpy/_core/code_generators/generate_numpy_api.py b/numpy/_core/code_generators/generate_numpy_api.py index d69725e581aa..9e86c9499546 100644 --- a/numpy/_core/code_generators/generate_numpy_api.py +++ b/numpy/_core/code_generators/generate_numpy_api.py @@ -227,6 +227,7 @@ def do_generate_api(targets, sources): # Check multiarray api indexes multiarray_api_index = genapi.merge_api_dicts(multiarray_api) + unused_index_max = max(multiarray_api_index.get("__unused_indices__", 0)) genapi.check_api_dict(multiarray_api_index) numpyapi_list = genapi.get_api_functions('NUMPY_API', @@ -278,6 +279,10 @@ def do_generate_api(targets, sources): init_list.append(api_item.array_api_define()) module_list.append(api_item.internal_define()) + # In case we end with a "hole", append more NULLs + while len(init_list) <= unused_index_max: + init_list.append(" NULL") + # Write to header s = h_template % ('\n'.join(module_list), '\n'.join(extension_list)) genapi.write_file(header_file, s) diff --git a/numpy/_core/code_generators/numpy_api.py b/numpy/_core/code_generators/numpy_api.py index 7dbaeff4940b..30e2222e557e 100644 --- a/numpy/_core/code_generators/numpy_api.py +++ b/numpy/_core/code_generators/numpy_api.py @@ -94,6 +94,7 @@ def get_annotations(): # NOTE: The Slots 320-360 are defined in `_experimental_dtype_api.h` # and filled explicitly outside the code generator as the metaclass # makes them tricky to expose. (This may be refactored.) + # Slot 366, 367, 368 are the abstract DTypes # End 2.0 API } @@ -107,7 +108,8 @@ def get_annotations(): 103, 115, 117, 122, 163, 164, 171, 173, 197, 201, 202, 208, 219, 220, 221, 222, 223, 278, 291, 293, 294, 295, 301] - + list(range(320, 361)) # range reserved DType class slots + # range/slots reserved DType classes (see _public_dtype_api_table.h): + + list(range(320, 361)) + [366, 367, 368] ), 'PyArray_GetNDArrayCVersion': (0,), # Unused slot 40, was `PyArray_SetNumericOps` diff --git a/numpy/_core/include/numpy/_public_dtype_api_table.h b/numpy/_core/include/numpy/_public_dtype_api_table.h index 5fbbdd785e4e..51f390540627 100644 --- a/numpy/_core/include/numpy/_public_dtype_api_table.h +++ b/numpy/_core/include/numpy/_public_dtype_api_table.h @@ -4,6 +4,9 @@ * * These definitions are only relevant for the public API and we reserve * the slots 320-360 in the API table generation for this (currently). + * + * TODO: This file should be consolidated with the API table generation + * (although not sure the current generation is worth preserving). */ #ifndef NUMPY_CORE_INCLUDE_NUMPY__PUBLIC_DTYPE_API_TABLE_H_ #define NUMPY_CORE_INCLUDE_NUMPY__PUBLIC_DTYPE_API_TABLE_H_ @@ -61,17 +64,21 @@ /* Object/Void */ #define PyArray_ObjectDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[33]) #define PyArray_VoidDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[34]) -/* Abstract */ -#define PyArray_PyIntAbstractDType \ - (*(PyArray_DTypeMeta *)(PyArray_API + 320)[35]) -#define PyArray_PyFloatAbstractDType \ - (*(PyArray_DTypeMeta *)(PyArray_API + 320)[36]) -#define PyArray_PyComplexAbstractDType \ - (*(PyArray_DTypeMeta *)(PyArray_API + 320)[37]) +/* Python types (used as markers for scalars) */ +#define PyArray_PyLongDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[35]) +#define PyArray_PyFloatDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[36]) +#define PyArray_PyComplexDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[37]) +/* Default integer type */ #define PyArray_DefaultIntDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[38]) /* New non-legacy DTypes follow in the order they were added */ #define PyArray_StringDType (*(PyArray_DTypeMeta *)(PyArray_API + 320)[39]) -/* NOTE: offset 40 is free, after that a new range will need to be used */ + +/* NOTE: offset 40 is free */ + +/* Need to start with a larger offset again for the abstract classes: */ +#define PyArray_IntAbstractDType (*(PyArray_DTypeMeta *)PyArray_API[366]) +#define PyArray_FloatAbstractDType (*(PyArray_DTypeMeta *)PyArray_API[367]) +#define PyArray_ComplexAbstractDType (*(PyArray_DTypeMeta *)PyArray_API[368]) #endif /* NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION */ diff --git a/numpy/_core/meson.build b/numpy/_core/meson.build index 8fc377aeedde..b1b7659b9eb1 100644 --- a/numpy/_core/meson.build +++ b/numpy/_core/meson.build @@ -347,7 +347,7 @@ max_opt = { 'msvc': ['/O2'], 'intel-cl': ['/O3'], }.get(compiler_id, ['-O3']) -max_opt = cc.has_multi_arguments(max_opt) ? max_opt : [] +max_opt = cc.has_multi_arguments(max_opt) and get_option('buildtype') != 'debug' ? max_opt : [] # Optional GCC compiler builtins and their call arguments. # If given, a required header and definition name (HAVE_ prepended) diff --git a/numpy/_core/numeric.py b/numpy/_core/numeric.py index 429620da5359..d5116cee2756 100644 --- a/numpy/_core/numeric.py +++ b/numpy/_core/numeric.py @@ -2142,7 +2142,7 @@ def base_repr(number, base=2, padding=0): elif base < 2: raise ValueError("Bases less than 2 not handled in base_repr.") - num = abs(number) + num = abs(int(number)) res = [] while num: res.append(digits[num % base]) diff --git a/numpy/_core/src/multiarray/abstractdtypes.c b/numpy/_core/src/multiarray/abstractdtypes.c index 3142411b2b61..8d00084f0efe 100644 --- a/numpy/_core/src/multiarray/abstractdtypes.c +++ b/numpy/_core/src/multiarray/abstractdtypes.c @@ -21,7 +21,7 @@ int_default_descriptor(PyArray_DTypeMeta* NPY_UNUSED(cls)) } static PyArray_Descr * -discover_descriptor_from_pyint( +discover_descriptor_from_pylong( PyArray_DTypeMeta *NPY_UNUSED(cls), PyObject *obj) { assert(PyLong_Check(obj)); @@ -85,33 +85,49 @@ discover_descriptor_from_pycomplex( NPY_NO_EXPORT int initialize_and_map_pytypes_to_dtypes() { - ((PyTypeObject *)&PyArray_PyIntAbstractDType)->tp_base = &PyArrayDescr_Type; - PyArray_PyIntAbstractDType.scalar_type = &PyLong_Type; - if (PyType_Ready((PyTypeObject *)&PyArray_PyIntAbstractDType) < 0) { + if (PyType_Ready((PyTypeObject *)&PyArray_IntAbstractDType) < 0) { return -1; } - ((PyTypeObject *)&PyArray_PyFloatAbstractDType)->tp_base = &PyArrayDescr_Type; - PyArray_PyFloatAbstractDType.scalar_type = &PyFloat_Type; - if (PyType_Ready((PyTypeObject *)&PyArray_PyFloatAbstractDType) < 0) { + if (PyType_Ready((PyTypeObject *)&PyArray_FloatAbstractDType) < 0) { return -1; } - ((PyTypeObject *)&PyArray_PyComplexAbstractDType)->tp_base = &PyArrayDescr_Type; - PyArray_PyComplexAbstractDType.scalar_type = &PyComplex_Type; - if (PyType_Ready((PyTypeObject *)&PyArray_PyComplexAbstractDType) < 0) { + if (PyType_Ready((PyTypeObject *)&PyArray_ComplexAbstractDType) < 0) { + return -1; + } + /* + * Delayed assignments to avoid "error C2099: initializer is not a constant" + * in windows compilers. Can hopefully be done in structs in the future. + */ + ((PyTypeObject *)&PyArray_PyLongDType)->tp_base = + (PyTypeObject *)&PyArray_IntAbstractDType; + PyArray_PyLongDType.scalar_type = &PyLong_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyLongDType) < 0) { + return -1; + } + ((PyTypeObject *)&PyArray_PyFloatDType)->tp_base = + (PyTypeObject *)&PyArray_FloatAbstractDType; + PyArray_PyFloatDType.scalar_type = &PyFloat_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyFloatDType) < 0) { + return -1; + } + ((PyTypeObject *)&PyArray_PyComplexDType)->tp_base = + (PyTypeObject *)&PyArray_ComplexAbstractDType; + PyArray_PyComplexDType.scalar_type = &PyComplex_Type; + if (PyType_Ready((PyTypeObject *)&PyArray_PyComplexDType) < 0) { return -1; } /* Register the new DTypes for discovery */ if (_PyArray_MapPyTypeToDType( - &PyArray_PyIntAbstractDType, &PyLong_Type, NPY_FALSE) < 0) { + &PyArray_PyLongDType, &PyLong_Type, NPY_FALSE) < 0) { return -1; } if (_PyArray_MapPyTypeToDType( - &PyArray_PyFloatAbstractDType, &PyFloat_Type, NPY_FALSE) < 0) { + &PyArray_PyFloatDType, &PyFloat_Type, NPY_FALSE) < 0) { return -1; } if (_PyArray_MapPyTypeToDType( - &PyArray_PyComplexAbstractDType, &PyComplex_Type, NPY_FALSE) < 0) { + &PyArray_PyComplexDType, &PyComplex_Type, NPY_FALSE) < 0) { return -1; } @@ -205,7 +221,7 @@ float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) return NPY_DT_NewRef(&PyArray_DoubleDType); } } - else if (other == &PyArray_PyIntAbstractDType) { + else if (other == &PyArray_PyLongDType) { Py_INCREF(cls); return cls; } @@ -261,8 +277,8 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) return res; } - else if (other == &PyArray_PyIntAbstractDType || - other == &PyArray_PyFloatAbstractDType) { + else if (other == &PyArray_PyLongDType || + other == &PyArray_PyFloatDType) { Py_INCREF(cls); return cls; } @@ -272,59 +288,93 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) /* - * TODO: These abstract DTypes also carry the dual role of representing - * `Floating`, `Complex`, and `Integer` (both signed and unsigned). - * They will have to be renamed and exposed in that capacity. + * Define abstract numerical DTypes that all regular ones can inherit from + * (in arraytypes.c.src). + * Here, also define types corresponding to the python scalars. */ -NPY_DType_Slots pyintabstractdtype_slots = { - .discover_descr_from_pyobject = discover_descriptor_from_pyint, +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_IntAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) + .tp_name = "numpy.dtypes._IntegerAbstractDType", + .tp_base = &PyArrayDescr_Type, + .tp_basicsize = sizeof(PyArray_Descr), + .tp_flags = Py_TPFLAGS_DEFAULT, + },}, + .type_num = -1, + .flags = NPY_DT_ABSTRACT, +}; + +NPY_DType_Slots pylongdtype_slots = { + .discover_descr_from_pyobject = discover_descriptor_from_pylong, .default_descr = int_default_descriptor, .common_dtype = int_common_dtype, }; -NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyIntAbstractDType = {{{ +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyLongDType = {{{ PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) - .tp_name = "numpy._IntegerAbstractDType", + .tp_name = "numpy.dtypes._PyLongDType", + .tp_base = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ .tp_basicsize = sizeof(PyArray_Descr), .tp_flags = Py_TPFLAGS_DEFAULT, },}, .type_num = -1, - .flags = NPY_DT_ABSTRACT, - .dt_slots = &pyintabstractdtype_slots, + .dt_slots = &pylongdtype_slots, + .scalar_type = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ }; +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_FloatAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) + .tp_name = "numpy.dtypes._FloatAbstractDType", + .tp_base = &PyArrayDescr_Type, + .tp_basicsize = sizeof(PyArray_Descr), + .tp_flags = Py_TPFLAGS_DEFAULT, + },}, + .type_num = -1, + .flags = NPY_DT_ABSTRACT, +}; -NPY_DType_Slots pyfloatabstractdtype_slots = { +NPY_DType_Slots pyfloatdtype_slots = { .discover_descr_from_pyobject = discover_descriptor_from_pyfloat, .default_descr = float_default_descriptor, .common_dtype = float_common_dtype, }; -NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{ +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatDType = {{{ PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) - .tp_name = "numpy._FloatAbstractDType", + .tp_name = "numpy.dtypes._PyFloatDType", + .tp_base = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ .tp_basicsize = sizeof(PyArray_Descr), .tp_flags = Py_TPFLAGS_DEFAULT, },}, .type_num = -1, - .flags = NPY_DT_ABSTRACT, - .dt_slots = &pyfloatabstractdtype_slots, + .dt_slots = &pyfloatdtype_slots, + .scalar_type = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ }; +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_ComplexAbstractDType = {{{ + PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) + .tp_name = "numpy.dtypes._ComplexAbstractDType", + .tp_base = &PyArrayDescr_Type, + .tp_basicsize = sizeof(PyArray_Descr), + .tp_flags = Py_TPFLAGS_DEFAULT, + },}, + .type_num = -1, + .flags = NPY_DT_ABSTRACT, +}; -NPY_DType_Slots pycomplexabstractdtype_slots = { +NPY_DType_Slots pycomplexdtype_slots = { .discover_descr_from_pyobject = discover_descriptor_from_pycomplex, .default_descr = complex_default_descriptor, .common_dtype = complex_common_dtype, }; -NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{ +NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexDType = {{{ PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0) - .tp_name = "numpy._ComplexAbstractDType", + .tp_name = "numpy.dtypes._PyComplexDType", + .tp_base = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ .tp_basicsize = sizeof(PyArray_Descr), .tp_flags = Py_TPFLAGS_DEFAULT, },}, .type_num = -1, - .flags = NPY_DT_ABSTRACT, - .dt_slots = &pycomplexabstractdtype_slots, + .dt_slots = &pycomplexdtype_slots, + .scalar_type = NULL, /* set in initialize_and_map_pytypes_to_dtypes */ }; diff --git a/numpy/_core/src/multiarray/abstractdtypes.h b/numpy/_core/src/multiarray/abstractdtypes.h index 212994a422ea..4533e99b635f 100644 --- a/numpy/_core/src/multiarray/abstractdtypes.h +++ b/numpy/_core/src/multiarray/abstractdtypes.h @@ -14,9 +14,12 @@ extern "C" { * may be necessary to make them (partially) public, to allow user-defined * dtypes to perform value based casting. */ -NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyIntAbstractDType; -NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyFloatAbstractDType; -NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyComplexAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_IntAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_FloatAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_ComplexAbstractDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyLongDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyFloatDType; +NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyComplexDType; NPY_NO_EXPORT int initialize_and_map_pytypes_to_dtypes(void); @@ -48,8 +51,8 @@ npy_mark_tmp_array_if_pyscalar( && (PyArray_ISINTEGER(arr) || PyArray_ISOBJECT(arr))) { ((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_INT; if (dtype != NULL) { - Py_INCREF(&PyArray_PyIntAbstractDType); - Py_SETREF(*dtype, &PyArray_PyIntAbstractDType); + Py_INCREF(&PyArray_PyLongDType); + Py_SETREF(*dtype, &PyArray_PyLongDType); } return 1; } @@ -57,8 +60,8 @@ npy_mark_tmp_array_if_pyscalar( && PyArray_TYPE(arr) == NPY_DOUBLE) { ((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_FLOAT; if (dtype != NULL) { - Py_INCREF(&PyArray_PyFloatAbstractDType); - Py_SETREF(*dtype, &PyArray_PyFloatAbstractDType); + Py_INCREF(&PyArray_PyFloatDType); + Py_SETREF(*dtype, &PyArray_PyFloatDType); } return 1; } @@ -66,8 +69,8 @@ npy_mark_tmp_array_if_pyscalar( && PyArray_TYPE(arr) == NPY_CDOUBLE) { ((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_COMPLEX; if (dtype != NULL) { - Py_INCREF(&PyArray_PyComplexAbstractDType); - Py_SETREF(*dtype, &PyArray_PyComplexAbstractDType); + Py_INCREF(&PyArray_PyComplexDType); + Py_SETREF(*dtype, &PyArray_PyComplexDType); } return 1; } diff --git a/numpy/_core/src/multiarray/array_coercion.c b/numpy/_core/src/multiarray/array_coercion.c index c2b924e093b5..f63dbbc77e1f 100644 --- a/numpy/_core/src/multiarray/array_coercion.c +++ b/numpy/_core/src/multiarray/array_coercion.c @@ -226,10 +226,10 @@ npy_discover_dtype_from_pytype(PyTypeObject *pytype) DType = Py_None; } else if (pytype == &PyFloat_Type) { - DType = (PyObject *)&PyArray_PyFloatAbstractDType; + DType = (PyObject *)&PyArray_PyFloatDType; } else if (pytype == &PyLong_Type) { - DType = (PyObject *)&PyArray_PyIntAbstractDType; + DType = (PyObject *)&PyArray_PyLongDType; } else { DType = PyDict_GetItem(_global_pytype_to_type_dict, diff --git a/numpy/_core/src/multiarray/array_converter.c b/numpy/_core/src/multiarray/array_converter.c index fd7ccd767056..8d31a8ac2a06 100644 --- a/numpy/_core/src/multiarray/array_converter.c +++ b/numpy/_core/src/multiarray/array_converter.c @@ -351,8 +351,8 @@ array_converter_result_type(PyArrayArrayConverterObject *self, "extra_dtype and ensure_inexact are mutually exclusive."); goto finish; } - Py_INCREF(&PyArray_PyFloatAbstractDType); - dt_info.dtype = &PyArray_PyFloatAbstractDType; + Py_INCREF(&PyArray_PyFloatDType); + dt_info.dtype = &PyArray_PyFloatDType; } if (dt_info.dtype != NULL) { diff --git a/numpy/_core/src/multiarray/arraytypes.c.src b/numpy/_core/src/multiarray/arraytypes.c.src index c266979c6f6f..49701bf8499a 100644 --- a/numpy/_core/src/multiarray/arraytypes.c.src +++ b/numpy/_core/src/multiarray/arraytypes.c.src @@ -18,6 +18,7 @@ #include "npy_config.h" #include "npy_sort.h" +#include "abstractdtypes.h" #include "common.h" #include "ctors.h" #include "convert_datatype.h" @@ -4351,10 +4352,16 @@ set_typeinfo(PyObject *dict) * CFloat, CDouble, CLongDouble, * Object, String, Unicode, Void, * Datetime, Timedelta# + * #scls = PyArrayDescr_Type, + * PyArray_IntAbstractDType*10, + * PyArray_FloatAbstractDType*4, + * PyArray_ComplexAbstractDType*3, + * PyArrayDescr_Type*6 # */ if (dtypemeta_wrap_legacy_descriptor( _builtin_descrs[NPY_@NAME@], &_Py@Name@_ArrFuncs, + (PyTypeObject *)&@scls@, "numpy.dtypes." NPY_@NAME@_Name "DType", #ifdef NPY_@NAME@_alias "numpy.dtypes." NPY_@NAME@_Alias "DType" diff --git a/numpy/_core/src/multiarray/common.c b/numpy/_core/src/multiarray/common.c index 5804c9cc9148..6759fbf19b53 100644 --- a/numpy/_core/src/multiarray/common.c +++ b/numpy/_core/src/multiarray/common.c @@ -48,7 +48,7 @@ _array_find_python_scalar_type(PyObject *op) } else if (PyLong_Check(op)) { return NPY_DT_CALL_discover_descr_from_pyobject( - &PyArray_PyIntAbstractDType, op); + &PyArray_PyLongDType, op); } return NULL; } diff --git a/numpy/_core/src/multiarray/convert_datatype.c b/numpy/_core/src/multiarray/convert_datatype.c index 92e6964fc21f..997f7aa5fd7e 100644 --- a/numpy/_core/src/multiarray/convert_datatype.c +++ b/numpy/_core/src/multiarray/convert_datatype.c @@ -1796,17 +1796,17 @@ PyArray_ResultType( all_descriptors[i_all] = NULL; /* no descriptor for py-scalars */ if (PyArray_FLAGS(arrs[i]) & NPY_ARRAY_WAS_PYTHON_INT) { /* This could even be an object dtype here for large ints */ - all_DTypes[i_all] = &PyArray_PyIntAbstractDType; + all_DTypes[i_all] = &PyArray_PyLongDType; if (PyArray_TYPE(arrs[i]) != NPY_LONG) { /* Not a "normal" scalar, so we cannot avoid the legacy path */ all_pyscalar = 0; } } else if (PyArray_FLAGS(arrs[i]) & NPY_ARRAY_WAS_PYTHON_FLOAT) { - all_DTypes[i_all] = &PyArray_PyFloatAbstractDType; + all_DTypes[i_all] = &PyArray_PyFloatDType; } else if (PyArray_FLAGS(arrs[i]) & NPY_ARRAY_WAS_PYTHON_COMPLEX) { - all_DTypes[i_all] = &PyArray_PyComplexAbstractDType; + all_DTypes[i_all] = &PyArray_PyComplexDType; } else { all_descriptors[i_all] = PyArray_DTYPE(arrs[i]); diff --git a/numpy/_core/src/multiarray/ctors.c b/numpy/_core/src/multiarray/ctors.c index 4c9d76991296..f8afb1e28b25 100644 --- a/numpy/_core/src/multiarray/ctors.c +++ b/numpy/_core/src/multiarray/ctors.c @@ -39,6 +39,13 @@ #include "umathmodule.h" + +NPY_NO_EXPORT const char *npy_no_copy_err_msg = ( + "Unable to avoid copy while creating an array as requested.\n" + "If using `np.array(obj, copy=False)` replace it with `np.asarray(obj)` " + "to allow a copy when needed (no behavior change in NumPy 1.x).\n" + "For more details, see https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword."); + /* * Reading from a file or a string. * @@ -1637,9 +1644,8 @@ PyArray_FromAny_int(PyObject *op, PyArray_Descr *in_descr, * If we got this far, we definitely have to create a copy, since we are * converting either from a scalar (cache == NULL) or a (nested) sequence. */ - if (flags & NPY_ARRAY_ENSURENOCOPY ) { - PyErr_SetString(PyExc_ValueError, - "Unable to avoid copy while creating an array."); + if (flags & NPY_ARRAY_ENSURENOCOPY) { + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); Py_DECREF(dtype); npy_free_coercion_cache(cache); return NULL; @@ -1847,8 +1853,7 @@ PyArray_CheckFromAny_int(PyObject *op, PyArray_Descr *in_descr, && !PyArray_ElementStrides(obj)) { PyObject *ret; if (requires & NPY_ARRAY_ENSURENOCOPY) { - PyErr_SetString(PyExc_ValueError, - "Unable to avoid copy while creating a new array."); + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); return NULL; } ret = PyArray_NewCopy((PyArrayObject *)obj, NPY_ANYORDER); @@ -1926,8 +1931,7 @@ PyArray_FromArray(PyArrayObject *arr, PyArray_Descr *newtype, int flags) if (copy) { if (flags & NPY_ARRAY_ENSURENOCOPY ) { - PyErr_SetString(PyExc_ValueError, - "Unable to avoid copy while creating an array from given array."); + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); Py_DECREF(newtype); return NULL; } @@ -2405,6 +2409,62 @@ PyArray_FromInterface(PyObject *origin) } + +/* + * Returns -1 and an error set or 0 with the original error cleared, must + * be called with an error set. + */ +static inline int +check_or_clear_and_warn_error_if_due_to_copy_kwarg(PyObject *kwnames) +{ + if (kwnames == NULL) { + return -1; /* didn't pass kwnames, can't possibly be the reason */ + } + if (!PyErr_ExceptionMatches(PyExc_TypeError)) { + return -1; + } + + /* + * In most cases, if we fail, we assume the error was unrelated to the + * copy kwarg and simply restore the original one. + */ + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + if (value == NULL) { + goto restore_error; + } + + PyObject *str_value = PyObject_Str(value); + if (str_value == NULL) { + goto restore_error; + } + int copy_kwarg_unsupported = PyUnicode_Contains( + str_value, npy_ma_str_array_err_msg_substr); + Py_DECREF(str_value); + if (copy_kwarg_unsupported == -1) { + goto restore_error; + } + if (copy_kwarg_unsupported) { + /* + * TODO: As of now NumPy 2.0, the this warning is only triggered with + * `copy=False` allowing downstream to not notice it. + */ + Py_DECREF(type); + Py_DECREF(value); + Py_XDECREF(traceback); + if (DEPRECATE("__array__ should implement the 'dtype' and " + "'copy' keyword argument") < 0) { + return -1; + } + return 0; + } + + restore_error: + PyErr_Restore(type, value, traceback); + return -1; +} + + /** * Check for an __array__ attribute and call it when it exists. * @@ -2447,74 +2507,61 @@ PyArray_FromArrayAttr_int( return Py_NotImplemented; } - PyObject *kwargs = PyDict_New(); + static PyObject *kwnames_is_copy = NULL; + if (kwnames_is_copy == NULL) { + kwnames_is_copy = Py_BuildValue("(s)", "copy"); + if (kwnames_is_copy == NULL) { + Py_DECREF(array_meth); + return NULL; + } + } + + Py_ssize_t nargs = 0; + PyObject *arguments[2]; + PyObject *kwnames = NULL; + + if (descr != NULL) { + arguments[0] = (PyObject *)descr; + nargs++; + } /* * Only if the value of `copy` isn't the default one, we try to pass it * along; for backwards compatibility we then retry if it fails because the * signature of the __array__ method being called does not have `copy`. */ - int copy_passed = 0; if (copy != -1) { - copy_passed = 1; - PyObject *copy_obj = copy == 1 ? Py_True : Py_False; - PyDict_SetItemString(kwargs, "copy", copy_obj); + kwnames = kwnames_is_copy; + arguments[nargs] = copy == 1 ? Py_True : Py_False; } - PyObject *args = descr != NULL ? PyTuple_Pack(1, descr) : PyTuple_New(0); - - new = PyObject_Call(array_meth, args, kwargs); + int must_copy_but_copy_kwarg_unimplemented = 0; + new = PyObject_Vectorcall(array_meth, arguments, nargs, kwnames); if (new == NULL) { - if (npy_ma_str_array_err_msg_substr == NULL) { + if (check_or_clear_and_warn_error_if_due_to_copy_kwarg(kwnames) < 0) { + /* Error was not cleared (or a new error set) */ Py_DECREF(array_meth); - Py_DECREF(args); - Py_DECREF(kwargs); return NULL; } - PyObject *type, *value, *traceback; - PyErr_Fetch(&type, &value, &traceback); - if (value != NULL) { - PyObject *str_value = PyObject_Str(value); - if (PyUnicode_Contains( - str_value, npy_ma_str_array_err_msg_substr) > 0) { - Py_DECREF(type); - Py_DECREF(value); - Py_XDECREF(traceback); - if (PyErr_WarnEx(PyExc_UserWarning, - "__array__ should implement 'dtype' and " - "'copy' keywords", 1) < 0) { - Py_DECREF(str_value); - Py_DECREF(array_meth); - Py_DECREF(args); - Py_DECREF(kwargs); - return NULL; - } - if (copy_passed) { /* try again */ - PyDict_DelItemString(kwargs, "copy"); - new = PyObject_Call(array_meth, args, kwargs); - if (new == NULL) { - Py_DECREF(str_value); - Py_DECREF(array_meth); - Py_DECREF(args); - Py_DECREF(kwargs); - return NULL; - } - } - } - Py_DECREF(str_value); + if (copy == 0) { + /* Cannot possibly avoid a copy, so error out. */ + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); + Py_DECREF(array_meth); + return NULL; } + /* + * The error seems to have been due to passing copy. We try to see + * more precisely what the message is and may try again. + */ + must_copy_but_copy_kwarg_unimplemented = 1; + new = PyObject_Vectorcall(array_meth, arguments, nargs, NULL); if (new == NULL) { - PyErr_Restore(type, value, traceback); Py_DECREF(array_meth); - Py_DECREF(args); - Py_DECREF(kwargs); return NULL; } } Py_DECREF(array_meth); - Py_DECREF(args); - Py_DECREF(kwargs); if (!PyArray_Check(new)) { PyErr_SetString(PyExc_ValueError, @@ -2523,6 +2570,10 @@ PyArray_FromArrayAttr_int( Py_DECREF(new); return NULL; } + if (must_copy_but_copy_kwarg_unimplemented) { + /* TODO: As of NumPy 2.0 this path is only reachable by C-API. */ + Py_SETREF(new, PyArray_NewCopy((PyArrayObject *)new, NPY_KEEPORDER)); + } return new; } diff --git a/numpy/_core/src/multiarray/ctors.h b/numpy/_core/src/multiarray/ctors.h index d2577f83ef96..fa1cd72e1478 100644 --- a/numpy/_core/src/multiarray/ctors.h +++ b/numpy/_core/src/multiarray/ctors.h @@ -1,6 +1,9 @@ #ifndef NUMPY_CORE_SRC_MULTIARRAY_CTORS_H_ #define NUMPY_CORE_SRC_MULTIARRAY_CTORS_H_ +extern NPY_NO_EXPORT const char *npy_no_copy_err_msg; + + NPY_NO_EXPORT PyObject * PyArray_NewFromDescr( PyTypeObject *subtype, PyArray_Descr *descr, int nd, diff --git a/numpy/_core/src/multiarray/dtypemeta.c b/numpy/_core/src/multiarray/dtypemeta.c index dd8ba326b98f..b444573f13e7 100644 --- a/numpy/_core/src/multiarray/dtypemeta.c +++ b/numpy/_core/src/multiarray/dtypemeta.c @@ -911,12 +911,15 @@ static PyArray_DTypeMeta * default_builtin_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) { assert(cls->type_num < NPY_NTYPES_LEGACY); - if (NPY_UNLIKELY(NPY_DT_is_abstract(other))) { + if (NPY_UNLIKELY(!NPY_DT_is_legacy(other))) { /* - * The abstract complex has a lower priority than the concrete inexact - * types to ensure the correct promotion with integers. + * Deal with the non-legacy types we understand: python scalars. + * These may have lower priority than the concrete inexact types, + * but can change the type of the result (complex, float, int). + * If our own DType is not numerical or has lower priority (e.g. + * integer but abstract one is float), signal not implemented. */ - if (other == &PyArray_PyComplexAbstractDType) { + if (other == &PyArray_PyComplexDType) { if (PyTypeNum_ISCOMPLEX(cls->type_num)) { Py_INCREF(cls); return cls; @@ -931,14 +934,14 @@ default_builtin_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) return NPY_DT_NewRef(&PyArray_CLongDoubleDType); } } - else if (other == &PyArray_PyFloatAbstractDType) { + else if (other == &PyArray_PyFloatDType) { if (PyTypeNum_ISCOMPLEX(cls->type_num) || PyTypeNum_ISFLOAT(cls->type_num)) { Py_INCREF(cls); return cls; } } - else if (other == &PyArray_PyIntAbstractDType) { + else if (other == &PyArray_PyLongDType) { if (PyTypeNum_ISCOMPLEX(cls->type_num) || PyTypeNum_ISFLOAT(cls->type_num) || PyTypeNum_ISINTEGER(cls->type_num) @@ -947,8 +950,10 @@ default_builtin_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other) return cls; } } + Py_INCREF(Py_NotImplemented); + return (PyArray_DTypeMeta *)Py_NotImplemented; } - if (!NPY_DT_is_legacy(other) || other->type_num > cls->type_num) { + if (other->type_num > cls->type_num) { /* * Let the more generic (larger type number) DType handle this * (note that half is after all others, which works out here.) @@ -1063,8 +1068,9 @@ object_common_dtype( * @returns 0 on success, -1 on failure. */ NPY_NO_EXPORT int -dtypemeta_wrap_legacy_descriptor(_PyArray_LegacyDescr *descr, - PyArray_ArrFuncs *arr_funcs, const char *name, const char *alias) +dtypemeta_wrap_legacy_descriptor( + _PyArray_LegacyDescr *descr, PyArray_ArrFuncs *arr_funcs, + PyTypeObject *dtype_super_class, const char *name, const char *alias) { int has_type_set = Py_TYPE(descr) == &PyArrayDescr_Type; @@ -1118,7 +1124,7 @@ dtypemeta_wrap_legacy_descriptor(_PyArray_LegacyDescr *descr, .tp_name = NULL, /* set below */ .tp_basicsize = sizeof(_PyArray_LegacyDescr), .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_base = &PyArrayDescr_Type, + .tp_base = NULL, /* set below */ .tp_new = (newfunc)legacy_dtype_default_new, .tp_doc = ( "DType class corresponding to the scalar type and dtype of " @@ -1131,11 +1137,12 @@ dtypemeta_wrap_legacy_descriptor(_PyArray_LegacyDescr *descr, /* Further fields are not common between DTypes */ }; memcpy(dtype_class, &prototype, sizeof(PyArray_DTypeMeta)); - /* Fix name of the Type*/ + /* Fix name and superclass of the Type*/ ((PyTypeObject *)dtype_class)->tp_name = name; + ((PyTypeObject *)dtype_class)->tp_base = dtype_super_class, dtype_class->dt_slots = dt_slots; - /* Let python finish the initialization (probably unnecessary) */ + /* Let python finish the initialization */ if (PyType_Ready((PyTypeObject *)dtype_class) < 0) { Py_DECREF(dtype_class); return -1; diff --git a/numpy/_core/src/multiarray/dtypemeta.h b/numpy/_core/src/multiarray/dtypemeta.h index 3315a7f80770..344b440b38e8 100644 --- a/numpy/_core/src/multiarray/dtypemeta.h +++ b/numpy/_core/src/multiarray/dtypemeta.h @@ -155,8 +155,8 @@ python_builtins_are_known_scalar_types( NPY_NO_EXPORT int dtypemeta_wrap_legacy_descriptor( - _PyArray_LegacyDescr *descr, PyArray_ArrFuncs *arr_funcs, - const char *name, const char *alias); + _PyArray_LegacyDescr *descr, PyArray_ArrFuncs *arr_funcs, + PyTypeObject *dtype_super_class, const char *name, const char *alias); NPY_NO_EXPORT void initialize_legacy_dtypemeta_aliases(_PyArray_LegacyDescr **_builtin_descrs); diff --git a/numpy/_core/src/multiarray/methods.c b/numpy/_core/src/multiarray/methods.c index b61cbed4c957..62cc25e64c1b 100644 --- a/numpy/_core/src/multiarray/methods.c +++ b/numpy/_core/src/multiarray/methods.c @@ -980,8 +980,7 @@ array_getarray(PyArrayObject *self, PyObject *args, PyObject *kwds) Py_DECREF(self); return ret; } else { // copy == NPY_COPY_NEVER - PyErr_SetString(PyExc_ValueError, - "Unable to avoid copy while creating an array from given array."); + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); Py_DECREF(self); return NULL; } diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c index 3c153adb83a8..f8f4c1603420 100644 --- a/numpy/_core/src/multiarray/multiarraymodule.c +++ b/numpy/_core/src/multiarray/multiarraymodule.c @@ -1586,8 +1586,7 @@ _array_fromobject_generic( } else { if (copy == NPY_COPY_NEVER) { - PyErr_SetString(PyExc_ValueError, - "Unable to avoid copy while creating a new array."); + PyErr_SetString(PyExc_ValueError, npy_no_copy_err_msg); goto finish; } ret = (PyArrayObject *)PyArray_NewCopy(oparr, order); diff --git a/numpy/_core/src/multiarray/public_dtype_api.c b/numpy/_core/src/multiarray/public_dtype_api.c index 73ab8a6b9f92..60dceae3275d 100644 --- a/numpy/_core/src/multiarray/public_dtype_api.c +++ b/numpy/_core/src/multiarray/public_dtype_api.c @@ -169,10 +169,15 @@ _fill_dtype_api(void *full_api_table[]) api_table[33] = &PyArray_ObjectDType; api_table[34] = &PyArray_VoidDType; /* Abstract */ - api_table[35] = &PyArray_PyIntAbstractDType; - api_table[36] = &PyArray_PyFloatAbstractDType; - api_table[37] = &PyArray_PyComplexAbstractDType; + api_table[35] = &PyArray_PyLongDType; + api_table[36] = &PyArray_PyFloatDType; + api_table[37] = &PyArray_PyComplexDType; api_table[38] = &PyArray_DefaultIntDType; /* Non-legacy DTypes that are built in to NumPy */ api_table[39] = &PyArray_StringDType; + + /* Abstract ones added directly: */ + full_api_table[366] = &PyArray_IntAbstractDType; + full_api_table[367] = &PyArray_FloatAbstractDType; + full_api_table[368] = &PyArray_ComplexAbstractDType; } diff --git a/numpy/_core/src/multiarray/usertypes.c b/numpy/_core/src/multiarray/usertypes.c index 92325247a60c..6b87345eab75 100644 --- a/numpy/_core/src/multiarray/usertypes.c +++ b/numpy/_core/src/multiarray/usertypes.c @@ -306,7 +306,8 @@ PyArray_RegisterDataType(PyArray_DescrProto *descr_proto) descr->type_num = typenum; /* update prototype to notice duplicate registration */ descr_proto->type_num = typenum; - if (dtypemeta_wrap_legacy_descriptor(descr, descr_proto->f, name, NULL) < 0) { + if (dtypemeta_wrap_legacy_descriptor( + descr, descr_proto->f, &PyArrayDescr_Type, name, NULL) < 0) { descr->type_num = -1; NPY_NUMUSERTYPES--; /* Override the type, it might be wrong and then decref crashes */ diff --git a/numpy/_core/src/umath/dispatching.c b/numpy/_core/src/umath/dispatching.c index fbba26ea247b..b7f2fb3d9caf 100644 --- a/numpy/_core/src/umath/dispatching.c +++ b/numpy/_core/src/umath/dispatching.c @@ -589,8 +589,7 @@ _make_new_typetup( none_count++; } else { - if (!NPY_DT_is_legacy(signature[i]) - || NPY_DT_is_abstract(signature[i])) { + if (!NPY_DT_is_legacy(signature[i])) { /* * The legacy type resolution can't deal with these. * This path will return `None` or so in the future to diff --git a/numpy/_core/src/umath/special_integer_comparisons.cpp b/numpy/_core/src/umath/special_integer_comparisons.cpp index 9e0c9481960b..05026be96e67 100644 --- a/numpy/_core/src/umath/special_integer_comparisons.cpp +++ b/numpy/_core/src/umath/special_integer_comparisons.cpp @@ -177,7 +177,7 @@ resolve_descriptors_with_scalars( { int value_range = 0; - npy_bool first_is_pyint = dtypes[0] == &PyArray_PyIntAbstractDType; + npy_bool first_is_pyint = dtypes[0] == &PyArray_PyLongDType; int arr_idx = first_is_pyint? 1 : 0; int scalar_idx = first_is_pyint? 0 : 1; PyObject *scalar = input_scalars[scalar_idx]; @@ -327,7 +327,7 @@ template static int add_dtype_loops(PyObject *umath, PyArrayMethod_Spec *spec, PyObject *info) { - PyArray_DTypeMeta *PyInt = &PyArray_PyIntAbstractDType; + PyArray_DTypeMeta *PyInt = &PyArray_PyLongDType; PyObject *name = PyUnicode_FromString(comp_name(comp)); if (name == nullptr) { @@ -441,7 +441,7 @@ init_special_int_comparisons(PyObject *umath) * `np.equal(2, 4)` (with two python integers) use an object loop. */ PyObject *dtype_tuple = PyTuple_Pack(3, - &PyArray_PyIntAbstractDType, &PyArray_PyIntAbstractDType, Bool); + &PyArray_PyLongDType, &PyArray_PyLongDType, Bool); if (dtype_tuple == NULL) { goto finish; } diff --git a/numpy/_core/src/umath/stringdtype_ufuncs.cpp b/numpy/_core/src/umath/stringdtype_ufuncs.cpp index 2380fa9495bd..de6ac4ebdfa3 100644 --- a/numpy/_core/src/umath/stringdtype_ufuncs.cpp +++ b/numpy/_core/src/umath/stringdtype_ufuncs.cpp @@ -2121,7 +2121,7 @@ string_unicode_bool_output_promoter( static int is_integer_dtype(PyArray_DTypeMeta *DType) { - if (DType == &PyArray_PyIntAbstractDType) { + if (DType == &PyArray_PyLongDType) { return 1; } else if (DType == &PyArray_Int8DType) { @@ -2522,23 +2522,11 @@ init_stringdtype_ufuncs(PyObject *umath) INIT_MULTIPLY(Int64, int64); INIT_MULTIPLY(UInt64, uint64); - // This is needed so the generic promoters defined after this don't match - // for np.multiply(string_array, string_array) - - PyArray_DTypeMeta *hdtypes[] = { - &PyArray_StringDType, - &PyArray_StringDType, - &PyArray_StringDType}; - - if (add_promoter(umath, "multiply", hdtypes, 3, string_multiply_promoter) < 0) { - return -1; - } - // all other integer dtypes are handled with a generic promoter PyArray_DTypeMeta *rdtypes[] = { &PyArray_StringDType, - (PyArray_DTypeMeta *)Py_None, + &PyArray_IntAbstractDType, &PyArray_StringDType}; if (add_promoter(umath, "multiply", rdtypes, 3, string_multiply_promoter) < 0) { @@ -2546,7 +2534,7 @@ init_stringdtype_ufuncs(PyObject *umath) } PyArray_DTypeMeta *ldtypes[] = { - (PyArray_DTypeMeta *)Py_None, + &PyArray_IntAbstractDType, &PyArray_StringDType, &PyArray_StringDType}; @@ -2566,7 +2554,7 @@ init_stringdtype_ufuncs(PyObject *umath) PyArray_DTypeMeta *findlike_promoter_dtypes[] = { &PyArray_StringDType, &PyArray_UnicodeDType, - &PyArray_PyIntAbstractDType, &PyArray_PyIntAbstractDType, + &PyArray_IntAbstractDType, &PyArray_IntAbstractDType, &PyArray_DefaultIntDType, }; @@ -2607,7 +2595,7 @@ init_stringdtype_ufuncs(PyObject *umath) PyArray_DTypeMeta *startswith_endswith_promoter_dtypes[] = { &PyArray_StringDType, &PyArray_UnicodeDType, - &PyArray_PyIntAbstractDType, &PyArray_PyIntAbstractDType, + &PyArray_IntAbstractDType, &PyArray_IntAbstractDType, &PyArray_BoolDType, }; @@ -2703,7 +2691,7 @@ init_stringdtype_ufuncs(PyObject *umath) PyArray_DTypeMeta *replace_promoter_pyint_dtypes[] = { &PyArray_StringDType, &PyArray_UnicodeDType, &PyArray_UnicodeDType, - &PyArray_PyIntAbstractDType, &PyArray_StringDType, + &PyArray_IntAbstractDType, &PyArray_StringDType, }; if (add_promoter(umath, "_replace", replace_promoter_pyint_dtypes, 5, diff --git a/numpy/_core/src/umath/ufunc_object.c b/numpy/_core/src/umath/ufunc_object.c index 63ef0d9080ca..4a09785113df 100644 --- a/numpy/_core/src/umath/ufunc_object.c +++ b/numpy/_core/src/umath/ufunc_object.c @@ -4065,17 +4065,15 @@ resolve_descriptors(int nop, original_dtypes[i] = PyArray_DTYPE(operands[i]); Py_INCREF(original_dtypes[i]); } - if (i < nin - && NPY_DT_is_abstract(signature[i]) - && inputs_tup != NULL) { - /* - * TODO: We may wish to allow any scalar here. Checking for - * abstract assumes this works out for Python scalars, - * which is the important case (especially for now). - * - * One possible check would be `DType->type == type(obj)`. - */ - input_scalars[i] = PyTuple_GET_ITEM(inputs_tup, i); + /* + * Check whether something is a scalar of the given type. + * We leave it to resolve_descriptors_with_scalars to deal + * with, e.g., only doing something special for python scalars. + */ + if (i < nin && inputs_tup != NULL) { + PyObject *input = PyTuple_GET_ITEM(inputs_tup, i); + input_scalars[i] = signature[i]->scalar_type == Py_TYPE(input) ? + input : NULL; } else { input_scalars[i] = NULL; @@ -6121,8 +6119,8 @@ py_resolve_dtypes_generic(PyUFuncObject *ufunc, npy_bool return_context, goto finish; } PyArray_ENABLEFLAGS(dummy_arrays[i], NPY_ARRAY_WAS_PYTHON_INT); - Py_INCREF(&PyArray_PyIntAbstractDType); - DTypes[i] = &PyArray_PyIntAbstractDType; + Py_INCREF(&PyArray_PyLongDType); + DTypes[i] = &PyArray_PyLongDType; promoting_pyscalars = NPY_TRUE; } else if (descr_obj == (PyObject *)&PyFloat_Type) { @@ -6132,8 +6130,8 @@ py_resolve_dtypes_generic(PyUFuncObject *ufunc, npy_bool return_context, goto finish; } PyArray_ENABLEFLAGS(dummy_arrays[i], NPY_ARRAY_WAS_PYTHON_FLOAT); - Py_INCREF(&PyArray_PyFloatAbstractDType); - DTypes[i] = &PyArray_PyFloatAbstractDType; + Py_INCREF(&PyArray_PyFloatDType); + DTypes[i] = &PyArray_PyFloatDType; promoting_pyscalars = NPY_TRUE; } else if (descr_obj == (PyObject *)&PyComplex_Type) { @@ -6143,8 +6141,8 @@ py_resolve_dtypes_generic(PyUFuncObject *ufunc, npy_bool return_context, goto finish; } PyArray_ENABLEFLAGS(dummy_arrays[i], NPY_ARRAY_WAS_PYTHON_COMPLEX); - Py_INCREF(&PyArray_PyComplexAbstractDType); - DTypes[i] = &PyArray_PyComplexAbstractDType; + Py_INCREF(&PyArray_PyComplexDType); + DTypes[i] = &PyArray_PyComplexDType; promoting_pyscalars = NPY_TRUE; } else if (descr_obj == Py_None) { diff --git a/numpy/_core/src/umath/wrapping_array_method.c b/numpy/_core/src/umath/wrapping_array_method.c index ee1db62abf68..fe065e175027 100644 --- a/numpy/_core/src/umath/wrapping_array_method.c +++ b/numpy/_core/src/umath/wrapping_array_method.c @@ -54,7 +54,7 @@ wrapping_method_resolve_descriptors( self->wrapped_meth, self->wrapped_dtypes, orig_given_descrs, orig_loop_descrs, view_offset); for (int i = 0; i < nargs; i++) { - Py_XDECREF(orig_given_descrs); + Py_XDECREF(orig_given_descrs[i]); } if (casting < 0) { return -1; @@ -62,7 +62,7 @@ wrapping_method_resolve_descriptors( int res = self->translate_loop_descrs( nin, nout, dtypes, given_descrs, orig_loop_descrs, loop_descrs); for (int i = 0; i < nargs; i++) { - Py_DECREF(orig_given_descrs); + Py_DECREF(orig_loop_descrs[i]); } if (res < 0) { return -1; diff --git a/numpy/_core/tests/test_multiarray.py b/numpy/_core/tests/test_multiarray.py index cdc418897bcc..88c17656a18b 100644 --- a/numpy/_core/tests/test_multiarray.py +++ b/numpy/_core/tests/test_multiarray.py @@ -8488,9 +8488,18 @@ def __array__(self, dtype=None): assert_array_equal(arr, base_arr) assert arr is base_arr - with pytest.warns(UserWarning, match=("should implement 'dtype' " - "and 'copy' keywords")): - np.array(a, copy=False) + # As of NumPy 2, explicitly passing copy=True does not trigger passing + # it to __array__ (deprecation warning is not triggered). + arr = np.array(a, copy=True) + assert_array_equal(arr, base_arr) + assert arr is not base_arr + + # And passing copy=False gives a deprecation warning, but also raises + # an error: + with pytest.warns(DeprecationWarning, match="__array__.*'copy'"): + with pytest.raises(ValueError, + match=r"Unable to avoid copy(.|\n)*numpy_2_0_migration_guide.html"): + np.array(a, copy=False) @pytest.mark.skipif(not HAS_REFCOUNT, reason="Python lacks refcounts") def test__array__reference_leak(self): diff --git a/numpy/_core/tests/test_numeric.py b/numpy/_core/tests/test_numeric.py index 4dfc45cb180a..7980973086f6 100644 --- a/numpy/_core/tests/test_numeric.py +++ b/numpy/_core/tests/test_numeric.py @@ -798,7 +798,7 @@ def setup_method(self): # Propagation of the RISC-V Unprivileged ISA for more details. # We disable the float32 sign test on riscv64 for -np.nan as the sign # of the NaN will be lost when it's converted to a float32. - if platform.processor() != 'riscv64': + if platform.machine() != 'riscv64': self.signf[3::6][self.ef[3::6]] = -np.nan self.signd[3::6][self.ed[3::6]] = -np.nan self.signf[4::6][self.ef[4::6]] = -0. @@ -1969,6 +1969,9 @@ def test_base_range(self): with assert_raises(ValueError): np.base_repr(1, 37) + def test_minimal_signed_int(self): + assert_equal(np.base_repr(np.int8(-128)), '-10000000') + def _test_array_equal_parametrizations(): """ diff --git a/numpy/_core/tests/test_stringdtype.py b/numpy/_core/tests/test_stringdtype.py index e38575dbc0dd..378d1886d26a 100644 --- a/numpy/_core/tests/test_stringdtype.py +++ b/numpy/_core/tests/test_stringdtype.py @@ -1252,6 +1252,27 @@ def test_binary(string_array, unicode_array, function_name, args): assert 0 +@pytest.mark.parametrize("function, expected", [ + (np.strings.find, [[2, -1], [1, -1]]), + (np.strings.startswith, [[False, False], [True, False]])]) +@pytest.mark.parametrize("start, stop", [ + (1, 4), + (np.int8(1), np.int8(4)), + (np.array([1, 1], dtype='u2'), np.array([4, 4], dtype='u2'))]) +def test_non_default_start_stop(function, start, stop, expected): + a = np.array([["--🐍--", "--🦜--"], + ["-🐍---", "-🦜---"]], "T") + indx = function(a, "🐍", start, stop) + assert_array_equal(indx, expected) + + +@pytest.mark.parametrize("count", [2, np.int8(2), np.array([2, 2], 'u2')]) +def test_replace_non_default_repeat(count): + a = np.array(["🐍--", "🦜-🦜-"], "T") + result = np.strings.replace(a, "🦜-", "🦜†", count) + assert_array_equal(result, np.array(["🐍--", "🦜†🦜†"], "T")) + + def test_strip_ljust_rjust_consistency(string_array, unicode_array): rjs = np.char.rjust(string_array, 1000) rju = np.char.rjust(unicode_array, 1000) diff --git a/numpy/_core/tests/test_ufunc.py b/numpy/_core/tests/test_ufunc.py index 6bdfde016cb2..dfe20bc577a9 100644 --- a/numpy/_core/tests/test_ufunc.py +++ b/numpy/_core/tests/test_ufunc.py @@ -2972,6 +2972,13 @@ def test_resolve_dtypes_basic(self): with pytest.raises(TypeError): np.add.resolve_dtypes((i4, f4, None), casting="no") + def test_resolve_dtypes_comparison(self): + i4 = np.dtype("i4") + i8 = np.dtype("i8") + b = np.dtype("?") + r = np.equal.resolve_dtypes((i4, i8, None)) + assert r == (i8, i8, b) + def test_weird_dtypes(self): S0 = np.dtype("S0") # S0 is often converted by NumPy to S1, but not here: diff --git a/numpy/_core/tests/test_umath.py b/numpy/_core/tests/test_umath.py index e01e6dd6346b..09942265f10d 100644 --- a/numpy/_core/tests/test_umath.py +++ b/numpy/_core/tests/test_umath.py @@ -1844,7 +1844,7 @@ def test_fpclass(self, stride): assert_equal(np.isnan(arr_f64[::stride]), nan[::stride]) assert_equal(np.isinf(arr_f32[::stride]), inf[::stride]) assert_equal(np.isinf(arr_f64[::stride]), inf[::stride]) - if platform.processor() == 'riscv64': + if platform.machine() == 'riscv64': # On RISC-V, many operations that produce NaNs, such as converting # a -NaN from f64 to f32, return a canonical NaN. The canonical # NaNs are always positive. See section 11.3 NaN Generation and @@ -1881,7 +1881,7 @@ def test_fp_noncontiguous(self, dtype): ncontig_out = out[1::3] contig_in = np.array(ncontig_in) - if platform.processor() == 'riscv64': + if platform.machine() == 'riscv64': # Disable the -np.nan signbit tests on riscv64. See comments in # test_fpclass for more details. data_rv = np.copy(data) @@ -1920,7 +1920,7 @@ def test_fp_noncontiguous(self, dtype): finite_split = np.array(np.array_split(finite, 2)) assert_equal(np.isnan(data_split), nan_split) assert_equal(np.isinf(data_split), inf_split) - if platform.processor() == 'riscv64': + if platform.machine() == 'riscv64': data_split_rv = np.array(np.array_split(data_rv, 2)) assert_equal(np.signbit(data_split_rv), sign_split) else: diff --git a/numpy/core/_multiarray_umath.py b/numpy/core/_multiarray_umath.py index a77e1557ba62..04cc88229aac 100644 --- a/numpy/core/_multiarray_umath.py +++ b/numpy/core/_multiarray_umath.py @@ -14,7 +14,7 @@ def __getattr__(attr_name): from ._utils import _raise_warning if attr_name in {"_ARRAY_API", "_UFUNC_API"}: - from numpy.version import short_version, release + from numpy.version import short_version import textwrap import traceback import sys @@ -22,42 +22,26 @@ def __getattr__(attr_name): msg = textwrap.dedent(f""" A module that was compiled using NumPy 1.x cannot be run in NumPy {short_version} as it may crash. To support both 1.x and 2.x - versions of NumPy, modules must be compiled against NumPy 2.0. + versions of NumPy, modules must be compiled with NumPy 2.0. + Some module may need to rebuild instead e.g. with 'pybind11>=2.12'. If you are a user of the module, the easiest solution will be to - either downgrade NumPy or update the failing module (if available). + downgrade to 'numpy<2' or try to upgrade the affected module. + We expect that some modules will need time to support NumPy 2. """) - if not release and short_version.startswith("2.0.0"): - # TODO: Can remove this after the release. - msg += textwrap.dedent("""\ - NOTE: When testing against pre-release versions of NumPy 2.0 - or building nightly wheels for it, it is necessary to ensure - the NumPy pre-release is used at build time. - The main way to ensure this is using no build isolation - and installing dependencies manually with NumPy. - - If your dependencies have the issue, check whether they - build nightly wheels build against NumPy 2.0. - - pybind11 note: If you see this message and do not see - any errors raised, it's possible this is due to a - package using an old version of pybind11 that should be - updated. - - """) - msg += "Traceback (most recent call last):" + tb_msg = "Traceback (most recent call last):" for line in traceback.format_stack()[:-1]: if "frozen importlib" in line: continue - msg += line - # Only print the message. This has two reasons (for now!): - # 1. Old NumPy replaced the error here making it never actually show - # in practice, thus raising alone would not be helpful. - # 2. pybind11 simply reaches into NumPy internals and requires a - # new release that includes the fix. That is missing as of 2023-11. - # But, it "conveniently" ignores the ABI version. - sys.stderr.write(msg) + tb_msg += line + + # Also print the message (with traceback). This is because old versions + # of NumPy unfortunately set up the import to replace (and hide) the + # error. The traceback shouldn't be needed, but e.g. pytest plugins + # seem to swallow it and we should be failing anyway... + sys.stderr.write(msg + tb_msg) + raise ImportError(msg) ret = getattr(_multiarray_umath, attr_name, None) if ret is None: diff --git a/numpy/tests/test_numpy_version.py b/numpy/tests/test_numpy_version.py index 61643426c8d7..d3abcb92c1c3 100644 --- a/numpy/tests/test_numpy_version.py +++ b/numpy/tests/test_numpy_version.py @@ -39,3 +39,16 @@ def test_short_version(): else: assert_(np.__version__.split("+")[0] == np.version.short_version, "short_version mismatch in development version") + + +def test_version_module(): + contents = set([s for s in dir(np.version) if not s.startswith('_')]) + expected = set([ + 'full_version', + 'git_revision', + 'release', + 'short_version', + 'version', + ]) + + assert contents == expected diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index 780e1fccb79e..618223705937 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -114,14 +114,14 @@ def test_NPY_NO_EXPORT(): "f2py", "fft", "lib", - "lib.format", # was this meant to be public? + "lib.array_utils", + "lib.format", + "lib.introspect", "lib.mixins", - "lib.recfunctions", + "lib.npyio", + "lib.recfunctions", # note: still needs cleaning, was forgotten for 2.0 "lib.scimath", "lib.stride_tricks", - "lib.npyio", - "lib.introspect", - "lib.array_utils", "linalg", "ma", "ma.extras", @@ -134,11 +134,12 @@ def test_NPY_NO_EXPORT(): "polynomial.legendre", "polynomial.polynomial", "random", + "strings", "testing", "testing.overrides", "typing", "typing.mypy_plugin", - "version" # Should be removed for NumPy 2.0 + "version", ]] if sys.version_info < (3, 12): PUBLIC_MODULES += [ @@ -158,7 +159,6 @@ def test_NPY_NO_EXPORT(): "numpy.char", "numpy.emath", "numpy.rec", - "numpy.strings", ] @@ -535,7 +535,7 @@ def test_core_shims_coherence(): # no need to add it to np.core if ( member_name.startswith("_") - or member_name == "tests" + or member_name in ["tests", "strings"] or f"numpy.{member_name}" in PUBLIC_ALIASED_MODULES ): continue diff --git a/tools/ci/emscripten/0001-do-not-set-meson-environment-variable-pyodide-gh-4502.patch b/tools/ci/emscripten/0001-do-not-set-meson-environment-variable-pyodide-gh-4502.patch deleted file mode 100644 index f06ea4eead19..000000000000 --- a/tools/ci/emscripten/0001-do-not-set-meson-environment-variable-pyodide-gh-4502.patch +++ /dev/null @@ -1,55 +0,0 @@ -From e08ebf0e90f632547c8ff5b396ec0c4ddd65aad4 Mon Sep 17 00:00:00 2001 -From: Gyeongjae Choi -Date: Sat, 10 Feb 2024 03:28:01 +0900 -Subject: [PATCH] Update numpy to 1.26.4 and don't set MESON env variable - (#4502) - -From meson-python 0.15, $MESON env variable is used to overwrite the meson binary -path. We don't want that behavior. ---- - pypabuild.py | 22 +++++++++++++++------- - 1 file changed, 15 insertions(+), 7 deletions(-) - -diff --git a/pypabuild.py b/pypabuild.py -index 9d0107a8..6961b14e 100644 ---- a/pypabuild.py -+++ b/pypabuild.py -@@ -40,6 +40,19 @@ AVOIDED_REQUIREMENTS = [ - "patchelf", - ] - -+# corresponding env variables for symlinks -+SYMLINK_ENV_VARS = { -+ "cc": "CC", -+ "c++": "CXX", -+ "ld": "LD", -+ "lld": "LLD", -+ "ar": "AR", -+ "gcc": "GCC", -+ "ranlib": "RANLIB", -+ "strip": "STRIP", -+ "gfortran": "FC", # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables -+} -+ - - def _gen_runner( - cross_build_env: Mapping[str, str], -@@ -207,13 +220,8 @@ def make_command_wrapper_symlinks(symlink_dir: Path) -> dict[str, str]: - symlink_path.unlink() - - symlink_path.symlink_to(pywasmcross_exe) -- if symlink == "c++": -- var = "CXX" -- elif symlink == "gfortran": -- var = "FC" # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables -- else: -- var = symlink.upper() -- env[var] = str(symlink_path) -+ if symlink in SYMLINK_ENV_VARS: -+ env[SYMLINK_ENV_VARS[symlink]] = str(symlink_path) - - return env - --- -2.39.3 (Apple Git-145) -