diff options
author | Charles Harris <charlesr.harris@gmail.com> | 2023-05-13 13:48:56 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-13 13:48:56 -0600 |
commit | 7cc7254a47c95d32c39010554d7c845ff26e7518 (patch) | |
tree | 04c8f46f08ef6c24c8b3c45daf1e12f96282ded4 | |
parent | 81caed6e3c34c4bf4b22b4f6167e816ba2a3f73c (diff) | |
parent | 51886ff6aa34362ab8d1d840457211beb7e9c37e (diff) | |
download | numpy-7cc7254a47c95d32c39010554d7c845ff26e7518.tar.gz |
Merge pull request #22539 from seberg/deprecate-find-common-type
DEP: Deprecate `np.find_common_type`
-rw-r--r-- | doc/release/upcoming_changes/22539.change.rst | 19 | ||||
-rw-r--r-- | doc/release/upcoming_changes/22539.deprecation.rst | 29 | ||||
-rw-r--r-- | numpy/__init__.pyi | 1 | ||||
-rw-r--r-- | numpy/core/numerictypes.py | 19 | ||||
-rw-r--r-- | numpy/core/numerictypes.pyi | 5 | ||||
-rw-r--r-- | numpy/core/tests/test_nep50_promotions.py | 8 | ||||
-rw-r--r-- | numpy/core/tests/test_numerictypes.py | 15 | ||||
-rw-r--r-- | numpy/core/tests/test_regression.py | 4 | ||||
-rw-r--r-- | numpy/lib/index_tricks.py | 32 | ||||
-rw-r--r-- | numpy/ma/tests/test_core.py | 2 | ||||
-rw-r--r-- | numpy/typing/tests/data/fail/numerictypes.pyi | 2 | ||||
-rw-r--r-- | numpy/typing/tests/data/pass/numerictypes.py | 5 | ||||
-rw-r--r-- | numpy/typing/tests/data/reveal/numerictypes.pyi | 2 |
13 files changed, 106 insertions, 37 deletions
diff --git a/doc/release/upcoming_changes/22539.change.rst b/doc/release/upcoming_changes/22539.change.rst new file mode 100644 index 000000000..9df62be30 --- /dev/null +++ b/doc/release/upcoming_changes/22539.change.rst @@ -0,0 +1,19 @@ +``np.r_[]`` and ``np.c_[]`` with certain scalar values +------------------------------------------------------ +In rare cases, using mainly ``np.r_`` with scalars can lead to different +results. The main potential changes are highlighted by the following:: + + >>> np.r_[np.arange(5, dtype=np.uint8), -1].dtype + int16 # rather than the default integer (int64 or int32) + >>> np.r_[np.arange(5, dtype=np.int8), 255] + array([ 0, 1, 2, 3, 4, 255], dtype=int16) + +Where the second example returned:: + + array([ 0, 1, 2, 3, 4, -1], dtype=int8) + +The first one is due to a signed integer scalar with an unsigned integer +array, while the second is due to ``255`` not fitting into ``int8`` and +NumPy currently inspecting values to make this work. +(Note that the second example is expected to change in the future due to +:ref:`NEP 50 <NEP50>`; it will then raise an error.) diff --git a/doc/release/upcoming_changes/22539.deprecation.rst b/doc/release/upcoming_changes/22539.deprecation.rst new file mode 100644 index 000000000..a30434b7e --- /dev/null +++ b/doc/release/upcoming_changes/22539.deprecation.rst @@ -0,0 +1,29 @@ +``np.find_common_type`` is deprecated +------------------------------------- +`numpy.find_common_type` is now deprecated and its use should be replaced +with either `numpy.result_type` or `numpy.promote_types`. +Most users leave the second ``scalar_types`` argument to ``find_common_type`` +as ``[]`` in which case ``np.result_type`` and ``np.promote_types`` are both +faster and more robust. +When not using ``scalar_types`` the main difference is that the replacement +intentionally converts non-native byte-order to native byte order. +Further, ``find_common_type`` returns ``object`` dtype rather than failing +promotion. This leads to differences when the inputs are not all numeric. +Importantly, this also happens for e.g. timedelta/datetime for which NumPy +promotion rules are currently sometimes surprising. + +When the ``scalar_types`` argument is not ``[]`` things are more complicated. +In most cases, using ``np.result_type`` and passing the Python values +``0``, ``0.0``, or ``0j`` has the same result as using ``int``, ``float``, +or ``complex`` in `scalar_types`. + +When ``scalar_types`` is constructed, ``np.result_type`` is the +correct replacement and it may be passed scalar values like ``np.float32(0.0)``. +Passing values other than 0, may lead to value-inspecting behavior +(which ``np.find_common_type`` never used and NEP 50 may change in the future). +The main possible change in behavior in this case, is when the array types +are signed integers and scalar types are unsigned. + +If you are unsure about how to replace a use of ``scalar_types`` or when +non-numeric dtypes are likely, please do not hesitate to open a NumPy issue +to ask for help. diff --git a/numpy/__init__.pyi b/numpy/__init__.pyi index ef1f9046a..da1e98dd6 100644 --- a/numpy/__init__.pyi +++ b/numpy/__init__.pyi @@ -396,7 +396,6 @@ from numpy.core.numerictypes import ( issubsctype as issubsctype, issubdtype as issubdtype, sctype2char as sctype2char, - find_common_type as find_common_type, nbytes as nbytes, cast as cast, ScalarType as ScalarType, diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py index 7a5948025..aea41bc2e 100644 --- a/numpy/core/numerictypes.py +++ b/numpy/core/numerictypes.py @@ -80,6 +80,7 @@ Exported symbols include: """ import numbers +import warnings from .multiarray import ( ndarray, array, dtype, datetime_data, datetime_as_string, @@ -599,6 +600,16 @@ def find_common_type(array_types, scalar_types): """ Determine common type following standard coercion rules. + .. deprecated:: NumPy 1.25 + + This function is deprecated, use `numpy.promote_types` or + `numpy.result_type` instead. To achieve semantics for the + `scalar_types` argument, use `numpy.result_type` and pass the Python + values `0`, `0.0`, or `0j`. + This will give the same results in almost all cases. + More information and rare exception can be found in the + `NumPy 1.25 release notes <https://numpy.org/devdocs/release/1.25.0-notes.html>`_. + Parameters ---------- array_types : sequence @@ -646,6 +657,14 @@ def find_common_type(array_types, scalar_types): dtype('complex128') """ + # Deprecated 2022-11-07, NumPy 1.25 + warnings.warn( + "np.find_common_type is deprecated. Please use `np.result_type` " + "or `np.promote_types`.\n" + "See https://numpy.org/devdocs/release/1.25.0-notes.html and the " + "docs for more information. (Deprecated NumPy 1.25)", + DeprecationWarning, stacklevel=2) + array_types = [dtype(x) for x in array_types] scalar_types = [dtype(x) for x in scalar_types] diff --git a/numpy/core/numerictypes.pyi b/numpy/core/numerictypes.pyi index d10e4822a..d05861b2e 100644 --- a/numpy/core/numerictypes.pyi +++ b/numpy/core/numerictypes.pyi @@ -118,11 +118,6 @@ def issubdtype(arg1: DTypeLike, arg2: DTypeLike) -> bool: ... def sctype2char(sctype: DTypeLike) -> str: ... -def find_common_type( - array_types: Iterable[DTypeLike], - scalar_types: Iterable[DTypeLike], -) -> dtype[Any]: ... - cast: _typedict[_CastFunc] nbytes: _typedict[int] typecodes: _TypeCodes diff --git a/numpy/core/tests/test_nep50_promotions.py b/numpy/core/tests/test_nep50_promotions.py index 10d91aa31..9e84c78c1 100644 --- a/numpy/core/tests/test_nep50_promotions.py +++ b/numpy/core/tests/test_nep50_promotions.py @@ -180,3 +180,11 @@ def test_nep50_integer_regression(): arr = np.array(1) assert (arr + 2**63).dtype == np.float64 assert (arr[()] + 2**63).dtype == np.float64 + +def test_nep50_with_axisconcatenator(): + # I promised that this will be an error in the future in the 1.25 + # release notes; test this (NEP 50 opt-in makes the deprecation an error). + np._set_promotion_state("weak") + + with pytest.raises(OverflowError): + np.r_[np.arange(5, dtype=np.int8), 255] diff --git a/numpy/core/tests/test_numerictypes.py b/numpy/core/tests/test_numerictypes.py index 2ac77a312..bab5bf246 100644 --- a/numpy/core/tests/test_numerictypes.py +++ b/numpy/core/tests/test_numerictypes.py @@ -339,23 +339,28 @@ class TestEmptyField: class TestCommonType: def test_scalar_loses1(self): - res = np.find_common_type(['f4', 'f4', 'i2'], ['f8']) + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type(['f4', 'f4', 'i2'], ['f8']) assert_(res == 'f4') def test_scalar_loses2(self): - res = np.find_common_type(['f4', 'f4'], ['i8']) + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type(['f4', 'f4'], ['i8']) assert_(res == 'f4') def test_scalar_wins(self): - res = np.find_common_type(['f4', 'f4', 'i2'], ['c8']) + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type(['f4', 'f4', 'i2'], ['c8']) assert_(res == 'c8') def test_scalar_wins2(self): - res = np.find_common_type(['u4', 'i4', 'i4'], ['f4']) + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type(['u4', 'i4', 'i4'], ['f4']) assert_(res == 'f8') def test_scalar_wins3(self): # doesn't go up to 'f16' on purpose - res = np.find_common_type(['u8', 'i8', 'i8'], ['f8']) + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type(['u8', 'i8', 'i8'], ['f8']) assert_(res == 'f8') class TestMultipleFields: diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index 141636034..841144790 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -1667,7 +1667,9 @@ class TestRegression: def test_find_common_type_boolean(self): # Ticket #1695 - assert_(np.find_common_type([], ['?', '?']) == '?') + with pytest.warns(DeprecationWarning, match="np.find_common_type"): + res = np.find_common_type([], ['?', '?']) + assert res == '?' def test_empty_mul(self): a = np.array([1.]) diff --git a/numpy/lib/index_tricks.py b/numpy/lib/index_tricks.py index abf9e1090..1da73dee5 100644 --- a/numpy/lib/index_tricks.py +++ b/numpy/lib/index_tricks.py @@ -7,7 +7,7 @@ import numpy as np from .._utils import set_module import numpy.core.numeric as _nx from numpy.core.numeric import ScalarType, array -from numpy.core.numerictypes import find_common_type, issubdtype +from numpy.core.numerictypes import issubdtype import numpy.matrixlib as matrixlib from .function_base import diff @@ -342,9 +342,8 @@ class AxisConcatenator: axis = self.axis objs = [] - scalars = [] - arraytypes = [] - scalartypes = [] + # dtypes or scalars for weak scalar handling in result_type + result_type_objs = [] for k, item in enumerate(key): scalar = False @@ -390,10 +389,8 @@ class AxisConcatenator: except (ValueError, TypeError) as e: raise ValueError("unknown special directive") from e elif type(item) in ScalarType: - newobj = array(item, ndmin=ndmin) - scalars.append(len(objs)) scalar = True - scalartypes.append(newobj.dtype) + newobj = item else: item_ndim = np.ndim(item) newobj = array(item, copy=False, subok=True, ndmin=ndmin) @@ -405,15 +402,20 @@ class AxisConcatenator: defaxes = list(range(ndmin)) axes = defaxes[:k1] + defaxes[k2:] + defaxes[k1:k2] newobj = newobj.transpose(axes) + objs.append(newobj) - if not scalar and isinstance(newobj, _nx.ndarray): - arraytypes.append(newobj.dtype) - - # Ensure that scalars won't up-cast unless warranted - final_dtype = find_common_type(arraytypes, scalartypes) - if final_dtype is not None: - for k in scalars: - objs[k] = objs[k].astype(final_dtype) + if scalar: + result_type_objs.append(item) + else: + result_type_objs.append(newobj.dtype) + + # Ensure that scalars won't up-cast unless warranted, for 0, drops + # through to error in concatenate. + if len(result_type_objs) != 0: + final_dtype = _nx.result_type(*result_type_objs) + # concatenate could do cast, but that can be overriden: + objs = [array(obj, copy=False, subok=True, + ndmin=ndmin, dtype=final_dtype) for obj in objs] res = self.concatenate(tuple(objs), axis=axis) diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 55b15fc34..6ab1d7e4f 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -4543,7 +4543,7 @@ class TestMaskedArrayFunctions: x = np.arange(4, dtype=np.int32) y = np.arange(4, dtype=np.float32) * 2.2 test = where(x > 1.5, y, x).dtype - control = np.find_common_type([np.int32, np.float32], []) + control = np.result_type(np.int32, np.float32) assert_equal(test, control) def test_where_broadcast(self): diff --git a/numpy/typing/tests/data/fail/numerictypes.pyi b/numpy/typing/tests/data/fail/numerictypes.pyi index a5c2814ef..ce5662d5e 100644 --- a/numpy/typing/tests/data/fail/numerictypes.pyi +++ b/numpy/typing/tests/data/fail/numerictypes.pyi @@ -9,5 +9,3 @@ np.maximum_sctype(1) # E: No overload variant np.issubsctype(1, np.int64) # E: incompatible type np.issubdtype(1, np.int64) # E: incompatible type - -np.find_common_type(np.int64, np.int64) # E: incompatible type diff --git a/numpy/typing/tests/data/pass/numerictypes.py b/numpy/typing/tests/data/pass/numerictypes.py index ab86f590d..63b6ad0e2 100644 --- a/numpy/typing/tests/data/pass/numerictypes.py +++ b/numpy/typing/tests/data/pass/numerictypes.py @@ -23,11 +23,6 @@ np.issubdtype(np.float64, np.float32) np.sctype2char("S1") np.sctype2char(list) -np.find_common_type([], [np.int64, np.float32, complex]) -np.find_common_type((), (np.int64, np.float32, complex)) -np.find_common_type([np.int64, np.float32], []) -np.find_common_type([np.float32], [np.int64, np.float64]) - np.cast[int] np.cast["i8"] np.cast[np.int64] diff --git a/numpy/typing/tests/data/reveal/numerictypes.pyi b/numpy/typing/tests/data/reveal/numerictypes.pyi index e1857557d..d4399e2b1 100644 --- a/numpy/typing/tests/data/reveal/numerictypes.pyi +++ b/numpy/typing/tests/data/reveal/numerictypes.pyi @@ -21,8 +21,6 @@ reveal_type(np.issubclass_(1, 1)) # E: Literal[False] reveal_type(np.sctype2char("S8")) # E: str reveal_type(np.sctype2char(list)) # E: str -reveal_type(np.find_common_type([np.int64], [np.int64])) # E: dtype[Any] - reveal_type(np.cast[int]) # E: _CastFunc reveal_type(np.cast["i8"]) # E: _CastFunc reveal_type(np.cast[np.int64]) # E: _CastFunc |