diff options
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/_pytesttester.py | 2 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/multiarraymodule.c | 35 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 9 | ||||
| -rw-r--r-- | numpy/core/tests/test_deprecations.py | 14 | ||||
| -rw-r--r-- | numpy/core/tests/test_records.py | 11 | ||||
| -rw-r--r-- | numpy/typing/__init__.py | 81 | ||||
| -rw-r--r-- | numpy/typing/_add_docstring.py | 96 |
7 files changed, 212 insertions, 36 deletions
diff --git a/numpy/_pytesttester.py b/numpy/_pytesttester.py index 33fee9a14..813e069a4 100644 --- a/numpy/_pytesttester.py +++ b/numpy/_pytesttester.py @@ -6,7 +6,7 @@ boiler plate for doing that is to put the following in the module ``__init__.py`` file:: from numpy._pytesttester import PytestTester - test = PytestTester(__name__).test + test = PytestTester(__name__) del PytestTester diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index 60b965845..af5949e73 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -1930,20 +1930,41 @@ array_scalar(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds) int alloc = 0; void *dptr; PyObject *ret; - + PyObject *base = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|O:scalar", kwlist, &PyArrayDescr_Type, &typecode, &obj)) { return NULL; } if (PyDataType_FLAGCHK(typecode, NPY_LIST_PICKLE)) { - if (!PySequence_Check(obj)) { - PyErr_SetString(PyExc_TypeError, - "found non-sequence while unpickling scalar with " - "NPY_LIST_PICKLE set"); + if (typecode->type_num == NPY_OBJECT) { + /* Deprecated 2020-11-24, NumPy 1.20 */ + if (DEPRECATE( + "Unpickling a scalar with object dtype is deprecated. " + "Object scalars should never be created. If this was a " + "properly created pickle, please open a NumPy issue. In " + "a best effort this returns the original object.") < 0) { + return NULL; + } + Py_INCREF(obj); + return obj; + } + /* We store the full array to unpack it here: */ + if (!PyArray_CheckExact(obj)) { + /* We pickle structured voids as arrays currently */ + PyErr_SetString(PyExc_RuntimeError, + "Unpickling NPY_LIST_PICKLE (structured void) scalar " + "requires an array. The pickle file may be corrupted?"); return NULL; } - dptr = &obj; + if (!PyArray_EquivTypes(PyArray_DESCR((PyArrayObject *)obj), typecode)) { + PyErr_SetString(PyExc_RuntimeError, + "Pickled array is not compatible with requested scalar " + "dtype. The pickle file may be corrupted?"); + return NULL; + } + base = obj; + dptr = PyArray_BYTES((PyArrayObject *)obj); } else if (PyDataType_FLAGCHK(typecode, NPY_ITEM_IS_POINTER)) { @@ -1993,7 +2014,7 @@ array_scalar(PyObject *NPY_UNUSED(ignored), PyObject *args, PyObject *kwds) dptr = PyBytes_AS_STRING(obj); } } - ret = PyArray_Scalar(dptr, typecode, NULL); + ret = PyArray_Scalar(dptr, typecode, base); /* free dptr which contains zeros */ if (alloc) { diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index f04bdbaa8..b8976d08f 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -1742,13 +1742,8 @@ gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args)) if (arr == NULL) { return NULL; } - /* arr.item() */ - PyObject *val = PyArray_GETITEM(arr, PyArray_DATA(arr)); - Py_DECREF(arr); - if (val == NULL) { - return NULL; - } - PyObject *tup = Py_BuildValue("NN", obj, val); + /* Use the whole array which handles sturctured void correctly */ + PyObject *tup = Py_BuildValue("NN", obj, arr); if (tup == NULL) { return NULL; } diff --git a/numpy/core/tests/test_deprecations.py b/numpy/core/tests/test_deprecations.py index 380b78f67..a67fe62c3 100644 --- a/numpy/core/tests/test_deprecations.py +++ b/numpy/core/tests/test_deprecations.py @@ -771,3 +771,17 @@ class TestDeprecateSubarrayDTypeDuringArrayCoercion(_DeprecationTestCase): np.array(arr, dtype="(2,2)f") self.assert_deprecated(check) + + +class TestDeprecatedUnpickleObjectScalar(_DeprecationTestCase): + # Deprecated 2020-11-24, NumPy 1.20 + """ + Technically, it should be impossible to create numpy object scalars, + but there was an unpickle path that would in theory allow it. That + path is invalid and must lead to the warning. + """ + message = "Unpickling a scalar with object dtype is deprecated." + + def test_deprecated(self): + ctor = np.core.multiarray.scalar + self.assert_deprecated(lambda: ctor(np.dtype("O"), 1)) diff --git a/numpy/core/tests/test_records.py b/numpy/core/tests/test_records.py index f28ad5ac9..4d4b4b515 100644 --- a/numpy/core/tests/test_records.py +++ b/numpy/core/tests/test_records.py @@ -424,7 +424,16 @@ class TestRecord: # make sure we did not pickle the address assert not isinstance(obj, bytes) - assert_raises(TypeError, ctor, dtype, 13) + assert_raises(RuntimeError, ctor, dtype, 13) + + # Test roundtrip: + dump = pickle.dumps(a[0]) + unpickled = pickle.loads(dump) + assert a[0] == unpickled + + # Also check the similar (impossible) "object scalar" path: + with pytest.warns(DeprecationWarning): + assert ctor(np.dtype("O"), data) is data def test_objview_record(self): # https://github.com/numpy/numpy/issues/2599 diff --git a/numpy/typing/__init__.py b/numpy/typing/__init__.py index cbde75462..e72e8fb4d 100644 --- a/numpy/typing/__init__.py +++ b/numpy/typing/__init__.py @@ -11,14 +11,11 @@ Typing (:mod:`numpy.typing`) typing-extensions_ package. Large parts of the NumPy API have PEP-484-style type annotations. In -addition, the following type aliases are available for users. +addition a number of type aliases are available to users, most prominently +the two below: -- ``typing.ArrayLike``: objects that can be converted to arrays -- ``typing.DTypeLike``: objects that can be converted to dtypes - -Roughly speaking, ``typing.ArrayLike`` is "objects that can be used as -inputs to ``np.array``" and ``typing.DTypeLike`` is "objects that can -be used as inputs to ``np.dtype``". +- `ArrayLike`: objects that can be converted to arrays +- `DTypeLike`: objects that can be converted to dtypes .. _typing-extensions: https://pypi.org/project/typing-extensions/ @@ -34,13 +31,13 @@ differences. ArrayLike ~~~~~~~~~ -The ``ArrayLike`` type tries to avoid creating object arrays. For +The `ArrayLike` type tries to avoid creating object arrays. For example, .. code-block:: python >>> np.array(x**2 for x in range(10)) - array(<generator object <genexpr> at 0x10c004cd0>, dtype=object) + array(<generator object <genexpr> at ...>, dtype=object) is valid NumPy code which will create a 0-dimensional object array. Type checkers will complain about the above example when using @@ -51,14 +48,14 @@ you can either use a ``# type: ignore`` comment: >>> np.array(x**2 for x in range(10)) # type: ignore -or explicitly type the array like object as ``Any``: +or explicitly type the array like object as `~typing.Any`: .. code-block:: python >>> from typing import Any >>> array_like: Any = (x**2 for x in range(10)) >>> np.array(array_like) - array(<generator object <genexpr> at 0x1192741d0>, dtype=object) + array(<generator object <genexpr> at ...>, dtype=object) ndarray ~~~~~~~ @@ -75,10 +72,10 @@ This sort of mutation is not allowed by the types. Users who want to write statically typed code should insted use the `numpy.ndarray.view` method to create a view of the array with a different dtype. -dtype -~~~~~ +DTypeLike +~~~~~~~~~ -The ``DTypeLike`` type tries to avoid creation of dtype objects using +The `DTypeLike` type tries to avoid creation of dtype objects using dictionary of fields like below: .. code-block:: python @@ -87,16 +84,43 @@ dictionary of fields like below: Although this is valid Numpy code, the type checker will complain about it, since its usage is discouraged. -Please see : https://numpy.org/devdocs/reference/arrays.dtypes.html +Please see : :ref:`Data type objects <arrays.dtypes>` + +Number Precision +~~~~~~~~~~~~~~~~ + +The precision of `numpy.number` subclasses is treated as a covariant generic +parameter (see :class:`~NBitBase`), simplifying the annoting of proccesses +involving precision-based casting. + +.. code-block:: python + + >>> from typing import TypeVar + >>> import numpy as np + >>> import numpy.typing as npt -NBitBase -~~~~~~~~ + >>> T = TypeVar("T", bound=npt.NBitBase) + >>> def func(a: "np.floating[T]", b: "np.floating[T]") -> "np.floating[T]": + ... ... -.. autoclass:: numpy.typing.NBitBase +Consequently, the likes of `~numpy.float16`, `~numpy.float32` and +`~numpy.float64` are still sub-types of `~numpy.floating`, but, contrary to +runtime, they're not necessarily considered as sub-classes. + +Timedelta64 +~~~~~~~~~~~ + +The `~numpy.timedelta64` class is not considered a subclass of `~numpy.signedinteger`, +the former only inheriting from `~numpy.generic` while static type checking. + +API +--- """ +# NOTE: The API section will be appended with additional entries +# further down in this file -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List if TYPE_CHECKING: import sys @@ -107,6 +131,17 @@ if TYPE_CHECKING: else: def final(f): return f +if not TYPE_CHECKING: + __all__ = ["ArrayLike", "DTypeLike", "NBitBase"] +else: + # Ensure that all objects within this module are accessible while + # static type checking. This includes private ones, as we need them + # for internal use. + # + # Declare to mypy that `__all__` is a list of strings without assigning + # an explicit value + __all__: List[str] + @final # Dissallow the creation of arbitrary `NBitBase` subclasses class NBitBase: @@ -170,7 +205,7 @@ class _16Bit(_32Bit): ... # type: ignore[misc] class _8Bit(_16Bit): ... # type: ignore[misc] # Clean up the namespace -del TYPE_CHECKING, final +del TYPE_CHECKING, final, List from ._scalars import ( _CharLike, @@ -186,6 +221,12 @@ from ._array_like import _SupportsArray, ArrayLike from ._shape import _Shape, _ShapeLike from ._dtype_like import _SupportsDType, _VoidDTypeLike, DTypeLike +if __doc__ is not None: + from ._add_docstring import _docstrings + __doc__ += _docstrings + __doc__ += '\n.. autoclass:: numpy.typing.NBitBase\n' + del _docstrings + from numpy._pytesttester import PytestTester test = PytestTester(__name__) del PytestTester diff --git a/numpy/typing/_add_docstring.py b/numpy/typing/_add_docstring.py new file mode 100644 index 000000000..8e39fe2c6 --- /dev/null +++ b/numpy/typing/_add_docstring.py @@ -0,0 +1,96 @@ +"""A module for creating docstrings for sphinx ``data`` domains.""" + +import re +import textwrap + +_docstrings_list = [] + + +def add_newdoc(name, value, doc): + _docstrings_list.append((name, value, doc)) + + +def _parse_docstrings(): + type_list_ret = [] + for name, value, doc in _docstrings_list: + s = textwrap.dedent(doc).replace("\n", "\n ") + + # Replace sections by rubrics + lines = s.split("\n") + new_lines = [] + indent = "" + for line in lines: + m = re.match(r'^(\s+)[-=]+\s*$', line) + if m and new_lines: + prev = textwrap.dedent(new_lines.pop()) + if prev == "Examples": + indent = "" + new_lines.append(f'{m.group(1)}.. rubric:: {prev}') + else: + indent = 4 * " " + new_lines.append(f'{m.group(1)}.. admonition:: {prev}') + new_lines.append("") + else: + new_lines.append(f"{indent}{line}") + s = "\n".join(new_lines) + + # Done. + type_list_ret.append(f""".. data:: {name}\n :value: {value}\n {s}""") + return "\n".join(type_list_ret) + + +add_newdoc('ArrayLike', 'typing.Union[...]', + """ + A `~typing.Union` representing objects that can be coerced into an `~numpy.ndarray`. + + Among others this includes the likes of: + + * Scalars. + * (Nested) sequences. + * Objects implementing the `~class.__array__` protocol. + + See Also + -------- + :term:`array_like`: + Any scalar or sequence that can be interpreted as an ndarray. + + Examples + -------- + .. code-block:: python + + >>> import numpy as np + >>> import numpy.typing as npt + + >>> def as_array(a: npt.ArrayLike) -> np.ndarray: + ... return np.array(a) + + """) + +add_newdoc('DTypeLike', 'typing.Union[...]', + """ + A `~typing.Union` representing objects that can be coerced into a `~numpy.dtype`. + + Among others this includes the likes of: + + * :class:`type` objects. + * Character codes or the names of :class:`type` objects. + * Objects with the ``.dtype`` attribute. + + See Also + -------- + :ref:`Specifying and constructing data types <arrays.dtypes.constructing>` + A comprehensive overview of all objects that can be coerced into data types. + + Examples + -------- + .. code-block:: python + + >>> import numpy as np + >>> import numpy.typing as npt + + >>> def as_dtype(d: npt.DTypeLike) -> np.dtype: + ... return np.dtype(d) + + """) + +_docstrings = _parse_docstrings() |
