Skip to content

Commit c74dbb4

Browse files
committed
API: Introduce np.isdtype function
1 parent 35c4319 commit c74dbb4

11 files changed

Lines changed: 190 additions & 31 deletions

File tree

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
`numpy.astype`
2-
--------------
1+
`numpy.isdtype`
2+
---------------
33

4-
`numpy.astype` was added to provide an Array API compatible alternative to
5-
`numpy.ndarray.astype` method.
4+
`numpy.isdtype` was added to provide a canonical way to classify NumPy's dtypes
5+
in compliance with Array API and using names standardized there.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
`numpy.astype`
2+
--------------
3+
4+
`numpy.astype` was added to provide an Array API compatible alternative to
5+
`numpy.ndarray.astype` method.

doc/source/reference/routines.dtype.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Data type testing
3535
.. autosummary::
3636
:toctree: generated/
3737

38+
isdtype
3839
issubdtype
3940

4041
Miscellaneous

numpy/__init__.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,26 +146,27 @@
146146
getbufsize, geterr, geterrcall, greater, greater_equal, half,
147147
heaviside, hstack, hypot, identity, iinfo, iinfo, indices, inexact,
148148
inf, inner, int16, int32, int64, int8, int_, intc, integer, intp,
149-
invert, is_busday, isclose, isfinite, isfortran, isinf, isnan, isnat,
150-
isscalar, issubdtype, lcm, ldexp, left_shift, less, less_equal,
151-
lexsort, linspace, little_endian, log, log10, log1p, log2, logaddexp,
152-
logaddexp2, logical_and, logical_not, logical_or, logical_xor,
153-
logspace, long, longdouble, longlong, matmul, max, maximum,
154-
may_share_memory, mean, memmap, min, min_scalar_type, minimum, mod,
155-
modf, moveaxis, multiply, nan, ndarray, ndim, nditer, negative,
156-
nested_iters, newaxis, nextafter, nonzero, not_equal, number, object_,
157-
ones, ones_like, outer, partition, permute_dims, pi, positive, pow,
158-
power, printoptions, prod, product, promote_types, ptp, put, putmask,
159-
rad2deg, radians, ravel, recarray, reciprocal, record, remainder,
160-
repeat, require, reshape, resize, result_type, right_shift, rint,
161-
roll, rollaxis, round, sctypeDict, searchsorted, set_printoptions,
162-
setbufsize, seterr, seterrcall, shape, shares_memory, short, sign,
163-
signbit, signedinteger, sin, single, sinh, size, sometrue, sort,
164-
spacing, sqrt, square, squeeze, stack, std, str_, subtract, sum,
165-
swapaxes, take, tan, tanh, tensordot, timedelta64, trace, transpose,
166-
true_divide, trunc, typecodes, ubyte, ufunc, uint, uint16, uint32,
167-
uint64, uint8, uintc, uintp, ulong, ulonglong, unsignedinteger,
168-
ushort, var, vdot, void, vstack, where, zeros, zeros_like
149+
invert, is_busday, isclose, isdtype, isfinite, isfortran, isinf,
150+
isnan, isnat, isscalar, issubdtype, lcm, ldexp, left_shift, less,
151+
less_equal, lexsort, linspace, little_endian, log, log10, log1p, log2,
152+
logaddexp, logaddexp2, logical_and, logical_not, logical_or,
153+
logical_xor, logspace, long, longdouble, longlong, matmul, max,
154+
maximum, may_share_memory, mean, memmap, min, min_scalar_type,
155+
minimum, mod, modf, moveaxis, multiply, nan, ndarray, ndim, nditer,
156+
negative, nested_iters, newaxis, nextafter, nonzero, not_equal,
157+
number, object_, ones, ones_like, outer, partition, permute_dims, pi,
158+
positive, pow, power, printoptions, prod, product, promote_types, ptp,
159+
put, putmask, rad2deg, radians, ravel, recarray, reciprocal, record,
160+
remainder, repeat, require, reshape, resize, result_type, right_shift,
161+
rint, roll, rollaxis, round, sctypeDict, searchsorted,
162+
set_printoptions, setbufsize, seterr, seterrcall, shape,
163+
shares_memory, short, sign, signbit, signedinteger, sin, single, sinh,
164+
size, sometrue, sort, spacing, sqrt, square, squeeze, stack, std,
165+
str_, subtract, sum, swapaxes, take, tan, tanh, tensordot,
166+
timedelta64, trace, transpose, true_divide, trunc, typecodes, ubyte,
167+
ufunc, uint, uint16, uint32, uint64, uint8, uintc, uintp, ulong,
168+
ulonglong, unsignedinteger, ushort, var, vdot, void, vstack, where,
169+
zeros, zeros_like
169170
)
170171

171172
# NOTE: It's still under discussion whether these aliases

numpy/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ from numpy._core.numeric import (
381381
)
382382

383383
from numpy._core.numerictypes import (
384+
isdtype as isdtype,
384385
issubdtype as issubdtype,
385386
cast as cast,
386387
ScalarType as ScalarType,

numpy/_core/numerictypes.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
import numbers
8080
import warnings
8181

82+
from . import multiarray as ma
8283
from .multiarray import (
8384
ndarray, array, dtype, datetime_data, datetime_as_string,
8485
busday_offset, busday_count, is_busday, busdaycalendar
@@ -89,7 +90,7 @@
8990
__all__ = [
9091
'ScalarType', 'typecodes', 'issubdtype', 'datetime_data',
9192
'datetime_as_string', 'busday_offset', 'busday_count',
92-
'is_busday', 'busdaycalendar'
93+
'is_busday', 'busdaycalendar', 'isdtype'
9394
]
9495

9596
# we don't need all these imports, but we need to keep them for compatibility
@@ -359,6 +360,90 @@ def issubsctype(arg1, arg2):
359360
return issubclass(obj2sctype(arg1), obj2sctype(arg2))
360361

361362

363+
@set_module('numpy')
364+
def isdtype(dtype, kind):
365+
"""
366+
Returns a boolean indicating whether a provided dtype is of a specified
367+
data type ``kind``. This function only supports Array API data types.
368+
369+
Parameters
370+
----------
371+
dtype : dtype
372+
The input dtype.
373+
kind : dtype or str or tuple of dtypes/strs.
374+
dtype or dtype kind. Allowed dtype kinds are:
375+
* ``bool`` : boolean kind
376+
* ``signed integer`` : signed integer data types
377+
* ``unsigned integer`` : unsigned integer data types
378+
* ``integral`` : integer data types
379+
* ``real floating`` : real-valued floating-point data types
380+
* ``complex floating`` : complex floating-point data types
381+
* ``numeric`` : numeric data types
382+
383+
Returns
384+
-------
385+
out : bool
386+
387+
See Also
388+
--------
389+
issubdtype
390+
391+
Examples
392+
--------
393+
>>> import numpy as np
394+
>>> np.isdtype(np.float32, np.float64)
395+
False
396+
>>> np.isdtype(np.int64, (np.uint64, "signed integer"))
397+
True
398+
399+
"""
400+
# validate and preprocess arguments
401+
if isinstance(dtype, ma.dtype):
402+
dtype = dtype.type
403+
404+
if dtype not in allTypes.values():
405+
raise TypeError(
406+
"input_dtype argument must be a NumPy dtype, "
407+
f"but it is a {dtype}."
408+
)
409+
410+
input_kinds = kind if isinstance(kind, tuple) else (kind,)
411+
412+
processed_kinds = set()
413+
414+
for kind in input_kinds:
415+
if kind == "bool":
416+
processed_kinds.add(allTypes["bool"])
417+
elif kind == "signed integer":
418+
processed_kinds.update(sctypes["int"])
419+
elif kind == "unsigned integer":
420+
processed_kinds.update(sctypes["uint"])
421+
elif kind == "integral":
422+
processed_kinds.update(sctypes["int"] + sctypes["uint"])
423+
elif kind == "real floating":
424+
processed_kinds.update(sctypes["float"])
425+
elif kind == "complex floating":
426+
processed_kinds.update(sctypes["complex"])
427+
elif kind == "numeric":
428+
processed_kinds.update(
429+
sctypes["int"] + sctypes["uint"] +
430+
sctypes["float"] + sctypes["complex"]
431+
)
432+
else:
433+
# validate and preprocess kind
434+
if isinstance(kind, ma.dtype):
435+
kind = kind.type
436+
if kind not in allTypes.values():
437+
raise TypeError(
438+
"kind argument must be comprised of NumPy dtypes or "
439+
f"strings only, but it is a {kind}."
440+
)
441+
442+
processed_kinds.add(kind)
443+
444+
return dtype in processed_kinds
445+
446+
362447
@set_module('numpy')
363448
def issubdtype(arg1, arg2):
364449
r"""

numpy/_core/numerictypes.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ def issubclass_(arg1: object, arg2: object) -> L[False]: ...
109109

110110
def issubsctype(arg1: DTypeLike, arg2: DTypeLike) -> bool: ...
111111

112+
def isdtype(
113+
dtype: dtype[Any] | type[Any],
114+
kind: DTypeLike | tuple[DTypeLike, ...]
115+
) -> bool: ...
116+
112117
def issubdtype(arg1: DTypeLike, arg2: DTypeLike) -> bool: ...
113118

114119
def sctype2char(sctype: DTypeLike) -> str: ...

numpy/_core/tests/test_numerictypes.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
import pytest
55
import numpy as np
66
import numpy._core.numerictypes as nt
7-
from numpy._core.numerictypes import issctype, sctype2char, maximum_sctype
8-
from numpy.testing import assert_, assert_equal, assert_raises, IS_PYPY
7+
from numpy._core.numerictypes import (
8+
issctype, sctype2char, maximum_sctype, sctypes
9+
)
10+
from numpy.testing import (
11+
assert_, assert_equal, assert_raises, assert_raises_regex, IS_PYPY
12+
)
913

1014
# This is the structure of the table used for plain objects:
1115
#
@@ -415,6 +419,61 @@ def test_nondtype_nonscalartype(self):
415419
assert np.issubdtype(np.float32, "f")
416420

417421

422+
class TestIsDType:
423+
"""
424+
Check correctness of `np.isdtype`. The test considers different argument
425+
configurations: `np.isdtype(dtype, k1)` and `np.isdtype(dtype, (k1, k2))`
426+
with concrete dtypes and dtype groups.
427+
"""
428+
dtype_group_dict = {
429+
"signed integer": sctypes["int"],
430+
"unsigned integer": sctypes["uint"],
431+
"integral": sctypes["int"] + sctypes["uint"],
432+
"real floating": sctypes["float"],
433+
"complex floating": sctypes["complex"],
434+
"numeric": (
435+
sctypes["int"] + sctypes["uint"] + sctypes["float"] +
436+
sctypes["complex"]
437+
)
438+
}
439+
440+
@pytest.mark.parametrize(
441+
"dtype,close_dtype",
442+
[
443+
(np.int64, np.int32), (np.uint64, np.uint32),
444+
(np.float64, np.float32), (np.complex128, np.complex64)
445+
]
446+
)
447+
@pytest.mark.parametrize(
448+
"dtype_group",
449+
[
450+
None, "signed integer", "unsigned integer", "integral",
451+
"real floating", "complex floating", "numeric"
452+
]
453+
)
454+
def test_isdtype(self, dtype, close_dtype, dtype_group):
455+
# First check if same dtypes return `true` and different ones
456+
# give `false` (even if they're close in the dtype hierarchy!)
457+
if dtype_group is None:
458+
assert np.isdtype(dtype, dtype)
459+
assert not np.isdtype(dtype, close_dtype)
460+
assert np.isdtype(dtype, (dtype, close_dtype))
461+
462+
# Check that dtype and a dtype group that it belongs to
463+
# return `true`, and `false` otherwise.
464+
elif dtype in self.dtype_group_dict[dtype_group]:
465+
assert np.isdtype(dtype, dtype_group)
466+
assert np.isdtype(dtype, (close_dtype, dtype_group))
467+
else:
468+
assert not np.isdtype(dtype, dtype_group)
469+
470+
def test_isdtype_invalid_args(self):
471+
with assert_raises_regex(TypeError, r".*must be a NumPy dtype.*"):
472+
np.isdtype("int64", np.int64)
473+
with assert_raises_regex(TypeError, r".*kind argument must.*"):
474+
np.isdtype(np.int64, "int64")
475+
476+
418477
class TestSctypeDict:
419478
def test_longdouble(self):
420479
assert_(np._core.sctypeDict['float64'] is not np.longdouble)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import numpy as np
22

3+
np.isdtype(1, np.int64) # E: incompatible type
4+
35
np.issubdtype(1, np.int64) # E: incompatible type

numpy/typing/tests/data/pass/numerictypes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import numpy as np
22

3+
np.isdtype(np.float64, (np.int64, np.float64))
4+
np.isdtype(np.int64, "signed integer")
5+
36
np.issubdtype("S1", np.bytes_)
47
np.issubdtype(np.float64, np.float32)
58

0 commit comments

Comments
 (0)