summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2023-05-13 13:48:56 -0600
committerGitHub <noreply@github.com>2023-05-13 13:48:56 -0600
commit7cc7254a47c95d32c39010554d7c845ff26e7518 (patch)
tree04c8f46f08ef6c24c8b3c45daf1e12f96282ded4
parent81caed6e3c34c4bf4b22b4f6167e816ba2a3f73c (diff)
parent51886ff6aa34362ab8d1d840457211beb7e9c37e (diff)
downloadnumpy-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.rst19
-rw-r--r--doc/release/upcoming_changes/22539.deprecation.rst29
-rw-r--r--numpy/__init__.pyi1
-rw-r--r--numpy/core/numerictypes.py19
-rw-r--r--numpy/core/numerictypes.pyi5
-rw-r--r--numpy/core/tests/test_nep50_promotions.py8
-rw-r--r--numpy/core/tests/test_numerictypes.py15
-rw-r--r--numpy/core/tests/test_regression.py4
-rw-r--r--numpy/lib/index_tricks.py32
-rw-r--r--numpy/ma/tests/test_core.py2
-rw-r--r--numpy/typing/tests/data/fail/numerictypes.pyi2
-rw-r--r--numpy/typing/tests/data/pass/numerictypes.py5
-rw-r--r--numpy/typing/tests/data/reveal/numerictypes.pyi2
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