diff options
| author | Matti Picus <matti.picus@gmail.com> | 2023-04-27 14:11:43 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-27 14:11:43 +0300 |
| commit | 6d34e34e7bc0dad1722958f00f3767d707c1849f (patch) | |
| tree | 782c8250bc6af42a1a514a958c3f1488abdb8ca2 /numpy | |
| parent | e2db7ca90b42d4cdc2c61a0ebc116879c35b7825 (diff) | |
| parent | 0dc83e270a7891ad27e38bb94d8567deaff5df51 (diff) | |
| download | numpy-6d34e34e7bc0dad1722958f00f3767d707c1849f.tar.gz | |
Merge pull request #23358 from seberg/dtype-class-in-types
API: Add DType classes into new `numpy.dtypes` module
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/__init__.py | 1 | ||||
| -rw-r--r-- | numpy/core/__init__.py | 13 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/arraytypes.c.src | 26 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/arraytypes.h.src | 98 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/dtypemeta.c | 70 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/dtypemeta.h | 3 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/scalartypes.c.src | 129 | ||||
| -rw-r--r-- | numpy/core/src/multiarray/usertypes.c | 33 | ||||
| -rw-r--r-- | numpy/core/tests/test_dtype.py | 32 | ||||
| -rw-r--r-- | numpy/dtypes.py | 77 | ||||
| -rw-r--r-- | numpy/dtypes.pyi | 43 | ||||
| -rw-r--r-- | numpy/meson.build | 2 | ||||
| -rw-r--r-- | numpy/tests/test_public_api.py | 1 |
13 files changed, 367 insertions, 161 deletions
diff --git a/numpy/__init__.py b/numpy/__init__.py index 83b42092f..ae8631387 100644 --- a/numpy/__init__.py +++ b/numpy/__init__.py @@ -140,6 +140,7 @@ else: from .core import * from . import compat from . import exceptions + from . import dtypes from . import lib # NOTE: to be revisited following future namespace cleanup. # See gh-14454 and gh-15672 for discussion. diff --git a/numpy/core/__init__.py b/numpy/core/__init__.py index 142c24ee0..08e717363 100644 --- a/numpy/core/__init__.py +++ b/numpy/core/__init__.py @@ -142,13 +142,14 @@ def _DType_reconstruct(scalar_type): def _DType_reduce(DType): - # To pickle a DType without having to add top-level names, pickle the - # scalar type for now (and assume that reconstruction will be possible). - if not DType._legacy: - # If we don't have a legacy DType, we should have a valid top level - # name available, so use it (i.e. `np.dtype` itself!) + # As types/classes, most DTypes can simply be pickled by their name: + if not DType._legacy or DType.__module__ == "numpy.dtypes": return DType.__name__ - scalar_type = DType.type # pickle the scalar type for reconstruction + + # However, user defined legacy dtypes (like rational) do not end up in + # `numpy.dtypes` as module and do not have a public class at all. + # For these, we pickle them by reconstructing them from the scalar type: + scalar_type = DType.type return _DType_reconstruct, (scalar_type,) diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 92ddd1ad0..b84625c77 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -4646,12 +4646,30 @@ set_typeinfo(PyObject *dict) * should be defined on the class and inherited to the scalar. * (NPY_HALF is the largest builtin one.) */ - for (i = 0; i <= NPY_HALF; i++) { - if (dtypemeta_wrap_legacy_descriptor(_builtin_descrs[i]) < 0) { - return -1; - } + /**begin repeat + * + * #NAME = BOOL, + * BYTE, UBYTE, SHORT, USHORT, INT, UINT, + * LONG, ULONG, LONGLONG, ULONGLONG, + * HALF, FLOAT, DOUBLE, LONGDOUBLE, + * CFLOAT, CDOUBLE, CLONGDOUBLE, + * OBJECT, STRING, UNICODE, VOID, + * DATETIME, TIMEDELTA# + */ + if (dtypemeta_wrap_legacy_descriptor( + _builtin_descrs[NPY_@NAME@], + "numpy.dtypes." NPY_@NAME@_Name "DType", +#ifdef NPY_@NAME@_alias + "numpy.dtypes." NPY_@NAME@_Alias "DType" +#else + NULL +#endif + ) < 0) { + return -1; } + /**end repeat**/ + /* * Add cast functions for the new types */ diff --git a/numpy/core/src/multiarray/arraytypes.h.src b/numpy/core/src/multiarray/arraytypes.h.src index aad464ccf..90a075dad 100644 --- a/numpy/core/src/multiarray/arraytypes.h.src +++ b/numpy/core/src/multiarray/arraytypes.h.src @@ -66,4 +66,102 @@ NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT int @TYPE@_@func@, NPY_CPU_DISPATCH_DECLARE(NPY_NO_EXPORT int BOOL_argmax, (npy_bool *ip, npy_intp n, npy_intp *max_ind, PyArrayObject *aip)) + +/* + * Define DType and scalar type names and aliases as used in Python. + */ + +/**begin repeat + * #NAME = BOOL, + * HALF, FLOAT, DOUBLE, LONGDOUBLE, + * CFLOAT, CDOUBLE, CLONGDOUBLE, + * STRING, UNICODE, VOID, OBJECT, + * DATETIME, TIMEDELTA# + * #name = bool_, + * float16, float32, float64, longdouble, + * complex64, complex128, clongdouble, + * bytes_, str_, void, object_, + * datetime64, timedelta64# + * #Name = Bool, + * Float16, Float32, Float64, LongDouble, + * Complex64, Complex128, CLongDouble, + * Bytes, Str, Void, Object, + * DateTime64, TimeDelta64# + */ +#define NPY_@NAME@_name "@name@" +#define NPY_@NAME@_Name "@Name@" + +/**end repeat**/ + + +/* + * Give integers different names when they are the same size (gh-9799). + * `intX` always refers to the first int of that size in the sequence + * `['LONG', 'LONGLONG', 'INT', 'SHORT', 'BYTE']`. + * Unfortunately, since the bitsize names are not strictly fixed, we add + * the C name for all integer types (as aliases). + * + * Right now, we do not define the C aliases for floats (which are always + * the same). + */ + +#if NPY_SIZEOF_BYTE == NPY_SIZEOF_SHORT + #define BYTE_not_size_named +#endif +#if NPY_SIZEOF_SHORT == NPY_SIZEOF_INT + #define SHORT_not_size_named +#endif +#if NPY_SIZEOF_INT == NPY_SIZEOF_LONG + #define INT_not_size_named +#endif +#if NPY_SIZEOF_LONGLONG == NPY_SIZEOF_LONG + #define LONGLONG_not_size_named +#endif + + +/**begin repeat + * #NAME = BYTE, SHORT, INT, LONG, LONGLONG, + * UBYTE, USHORT, UINT, ULONG, ULONGLONG# + * #CNAME = (BYTE, SHORT, INT, LONG, LONGLONG)*2# + * #cname = byte, short, intc, int_, longlong, + * ubyte, ushort, uintc, uint, ulonglong# + * #CName = Byte, Short, Int, Long, LongLong, + * UByte, UShort, UInt, ULong, ULongLong# + * #bitname = int*5, uint*5# + * #BitName = Int*5, UInt*5# + */ + +#ifdef @CNAME@_not_size_named + #define NPY_@NAME@_name "@cname@" + #define NPY_@NAME@_Name "@CName@" +#else + /* The C-name is considered just an alias for these: */ + #define NPY_@NAME@_alias "@cname@" + #define NPY_@NAME@_Alias "@CName@" + + /* The bitsof macro includes math, so cannot be stringified */ + #if NPY_BITSOF_@CNAME@ == 8 + #define NPY_@NAME@_name "@bitname@8" + #define NPY_@NAME@_Name "@BitName@8" + #elif NPY_BITSOF_@CNAME@ == 16 + #define NPY_@NAME@_name "@bitname@16" + #define NPY_@NAME@_Name "@BitName@16" + #elif NPY_BITSOF_@CNAME@ == 32 + #define NPY_@NAME@_name "@bitname@32" + #define NPY_@NAME@_Name "@BitName@32" + #elif NPY_BITSOF_@CNAME@ == 64 + #define NPY_@NAME@_name "@bitname@64" + #define NPY_@NAME@_Name "@BitName@64" + #else + #error "need to fix integer bit-length name code" + #endif +#endif + +/**end repeat**/ + +#undef BYTE_not_size_named +#undef SHORT_not_size_named +#undef INT_not_size_named +#undef LONGLONG_not_size_named + #endif /* NUMPY_CORE_SRC_MULTIARRAY_ARRAYTYPES_H_ */ diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c index f8c1b6617..4990afa18 100644 --- a/numpy/core/src/multiarray/dtypemeta.c +++ b/numpy/core/src/multiarray/dtypemeta.c @@ -9,7 +9,9 @@ #include <numpy/ndarraytypes.h> #include <numpy/arrayscalars.h> #include "npy_pycompat.h" +#include "npy_import.h" +#include "arraytypes.h" #include "common.h" #include "dtypemeta.h" #include "descriptor.h" @@ -726,12 +728,17 @@ object_common_dtype( * be a HeapType and its instances should be exact PyArray_Descr structs. * * @param descr The descriptor that should be wrapped. - * @param name The name for the DType, if NULL the type character is used. + * @param name The name for the DType. + * @param alias A second name which is also set to the new class for builtins + * (i.e. `np.types.LongDType` for `np.types.Int64DType`). + * Some may have more aliases, as `intp` is not its own thing, + * as of writing this, these are not added here. * * @returns 0 on success, -1 on failure. */ NPY_NO_EXPORT int -dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) +dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr, + const char *name, const char *alias) { int has_type_set = Py_TYPE(descr) == &PyArrayDescr_Type; @@ -758,47 +765,14 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) return -1; } - /* - * Note: we have no intention of freeing the memory again since this - * behaves identically to static type definition (see comment above). - * This is seems cleaner for the legacy API, in the new API both static - * and heap types are possible (some difficulty arises from the fact that - * these are instances of DTypeMeta and not type). - * In particular our own DTypes can be true static declarations. - * However, this function remains necessary for legacy user dtypes. - */ - - const char *scalar_name = descr->typeobj->tp_name; - /* - * We have to take only the name, and ignore the module to get - * a reasonable __name__, since static types are limited in this regard - * (this is not ideal, but not a big issue in practice). - * This is what Python does to print __name__ for static types. - */ - const char *dot = strrchr(scalar_name, '.'); - if (dot) { - scalar_name = dot + 1; - } - Py_ssize_t name_length = strlen(scalar_name) + 14; - - char *tp_name = PyMem_Malloc(name_length); - if (tp_name == NULL) { - PyErr_NoMemory(); - return -1; - } - - snprintf(tp_name, name_length, "numpy.dtype[%s]", scalar_name); - NPY_DType_Slots *dt_slots = PyMem_Malloc(sizeof(NPY_DType_Slots)); if (dt_slots == NULL) { - PyMem_Free(tp_name); return -1; } memset(dt_slots, '\0', sizeof(NPY_DType_Slots)); PyArray_DTypeMeta *dtype_class = PyMem_Malloc(sizeof(PyArray_DTypeMeta)); if (dtype_class == NULL) { - PyMem_Free(tp_name); PyMem_Free(dt_slots); return -1; } @@ -820,13 +794,19 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) .tp_flags = Py_TPFLAGS_DEFAULT, .tp_base = &PyArrayDescr_Type, .tp_new = (newfunc)legacy_dtype_default_new, + .tp_doc = ( + "DType class corresponding to the scalar type and dtype of " + "the same name.\n\n" + "Please see `numpy.dtype` for the typical way to create\n" + "dtype instances and :ref:`arrays.dtypes` for additional\n" + "information."), },}, .flags = NPY_DT_LEGACY, /* Further fields are not common between DTypes */ }; memcpy(dtype_class, &prototype, sizeof(PyArray_DTypeMeta)); /* Fix name of the Type*/ - ((PyTypeObject *)dtype_class)->tp_name = tp_name; + ((PyTypeObject *)dtype_class)->tp_name = name; dtype_class->dt_slots = dt_slots; /* Let python finish the initialization (probably unnecessary) */ @@ -921,6 +901,21 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr) /* Finally, replace the current class of the descr */ Py_SET_TYPE(descr, (PyTypeObject *)dtype_class); + /* And it to the types submodule if it is a builtin dtype */ + if (!PyTypeNum_ISUSERDEF(descr->type_num)) { + static PyObject *add_dtype_helper = NULL; + npy_cache_import("numpy.dtypes", "_add_dtype_helper", &add_dtype_helper); + if (add_dtype_helper == NULL) { + return -1; + } + + if (PyObject_CallFunction( + add_dtype_helper, + "Os", (PyObject *)dtype_class, alias) == NULL) { + return -1; + } + } + return 0; } @@ -958,7 +953,8 @@ static PyGetSetDef dtypemeta_getset[] = { static PyMemberDef dtypemeta_members[] = { {"type", - T_OBJECT, offsetof(PyArray_DTypeMeta, scalar_type), READONLY, NULL}, + T_OBJECT, offsetof(PyArray_DTypeMeta, scalar_type), READONLY, + "scalar type corresponding to the DType."}, {NULL, 0, 0, 0, NULL}, }; diff --git a/numpy/core/src/multiarray/dtypemeta.h b/numpy/core/src/multiarray/dtypemeta.h index 6dbfd1549..32212228c 100644 --- a/numpy/core/src/multiarray/dtypemeta.h +++ b/numpy/core/src/multiarray/dtypemeta.h @@ -140,7 +140,8 @@ python_builtins_are_known_scalar_types( PyArray_DTypeMeta *cls, PyTypeObject *pytype); NPY_NO_EXPORT int -dtypemeta_wrap_legacy_descriptor(PyArray_Descr *dtypem); +dtypemeta_wrap_legacy_descriptor( + PyArray_Descr *dtypem, const char *name, const char *alias); #ifdef __cplusplus } diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index 17163ef09..c721800be 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -15,6 +15,7 @@ #include "npy_pycompat.h" +#include "arraytypes.h" #include "npy_config.h" #include "mapping.h" #include "ctors.h" @@ -3581,7 +3582,7 @@ object_arrtype_call(PyObjectScalarObject *obj, PyObject *args, PyObject *kwds) NPY_NO_EXPORT PyTypeObject PyObjectArrType_Type = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "numpy.object_", + .tp_name = "numpy." NPY_OBJECT_name, .tp_basicsize = sizeof(PyObjectScalarObject), .tp_dealloc = (destructor)object_arrtype_dealloc, .tp_as_sequence = &object_arrtype_as_sequence, @@ -3617,60 +3618,29 @@ gen_arrtype_subscript(PyObject *self, PyObject *key) } -#define NAME_bool "bool" -#define NAME_void "void" -#define NAME_string "bytes" -#define NAME_unicode "str" - /**begin repeat - * #name = bool, string, unicode, void# - * #NAME = Bool, String, Unicode, Void# - * #ex = _,_,_,# + * #Name = Bool, + * Byte, Short, Int, Long, LongLong, + * UByte, UShort, UInt, ULong, ULongLong, + * Half, Float, Double, LongDouble, + * CFloat, CDouble, CLongDouble, + * String, Unicode, Void, + * Datetime, Timedelta# + * #NAME = BOOL, + * BYTE, SHORT, INT, LONG, LONGLONG, + * UBYTE, USHORT, UINT, ULONG, ULONGLONG, + * HALF, FLOAT, DOUBLE, LONGDOUBLE, + * CFLOAT, CDOUBLE, CLONGDOUBLE, + * STRING, UNICODE, VOID, + * DATETIME, TIMEDELTA# */ -NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "numpy." NAME_@name@ "@ex@", - .tp_basicsize = sizeof(Py@NAME@ScalarObject), -}; -/**end repeat**/ - -#undef NAME_bool -#undef NAME_void -#undef NAME_string -#undef NAME_unicode -/**begin repeat - * #NAME = Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong, - * ULongLong, Half, Float, Double, LongDouble, Datetime, Timedelta# - * #name = int*5, uint*5, float*4, datetime, timedelta# - * #CNAME = (CHAR, SHORT, INT, LONG, LONGLONG)*2, HALF, FLOAT, DOUBLE, - * LONGDOUBLE, DATETIME, TIMEDELTA# - */ -#if NPY_BITSOF_@CNAME@ == 8 -#define _THIS_SIZE "8" -#elif NPY_BITSOF_@CNAME@ == 16 -#define _THIS_SIZE "16" -#elif NPY_BITSOF_@CNAME@ == 32 -#define _THIS_SIZE "32" -#elif NPY_BITSOF_@CNAME@ == 64 -#define _THIS_SIZE "64" -#elif NPY_BITSOF_@CNAME@ == 80 -#define _THIS_SIZE "80" -#elif NPY_BITSOF_@CNAME@ == 96 -#define _THIS_SIZE "96" -#elif NPY_BITSOF_@CNAME@ == 128 -#define _THIS_SIZE "128" -#elif NPY_BITSOF_@CNAME@ == 256 -#define _THIS_SIZE "256" -#endif -NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { +NPY_NO_EXPORT PyTypeObject Py@Name@ArrType_Type = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "numpy.@name@" _THIS_SIZE, - .tp_basicsize = sizeof(Py@NAME@ScalarObject), + .tp_name = "numpy." NPY_@NAME@_name, + .tp_basicsize = sizeof(Py@Name@ScalarObject), }; - -#undef _THIS_SIZE /**end repeat**/ @@ -3679,37 +3649,6 @@ static PyMappingMethods gentype_as_mapping = { }; -/**begin repeat - * #NAME = CFloat, CDouble, CLongDouble# - * #name = complex*3# - * #CNAME = FLOAT, DOUBLE, LONGDOUBLE# - */ -#if NPY_BITSOF_@CNAME@ == 16 -#define _THIS_SIZE "32" -#elif NPY_BITSOF_@CNAME@ == 32 -#define _THIS_SIZE "64" -#elif NPY_BITSOF_@CNAME@ == 64 -#define _THIS_SIZE "128" -#elif NPY_BITSOF_@CNAME@ == 80 -#define _THIS_SIZE "160" -#elif NPY_BITSOF_@CNAME@ == 96 -#define _THIS_SIZE "192" -#elif NPY_BITSOF_@CNAME@ == 128 -#define _THIS_SIZE "256" -#elif NPY_BITSOF_@CNAME@ == 256 -#define _THIS_SIZE "512" -#endif - -NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { - PyVarObject_HEAD_INIT(0, 0) - .tp_name = "numpy.@name@" _THIS_SIZE, - .tp_basicsize = sizeof(Py@NAME@ScalarObject), - .tp_flags = Py_TPFLAGS_DEFAULT, -}; -#undef _THIS_SIZE - -/**end repeat**/ - /* * This table maps the built-in type numbers to their scalar * type numbers. Note that signed integers are mapped to INTNEG_SCALAR, @@ -4098,36 +4037,6 @@ initialize_numeric_types(void) PyArrayIter_Type.tp_iter = PyObject_SelfIter; PyArrayMapIter_Type.tp_iter = PyObject_SelfIter; - - /* - * Give types different names when they are the same size (gh-9799). - * `np.intX` always refers to the first int of that size in the sequence - * `['LONG', 'LONGLONG', 'INT', 'SHORT', 'BYTE']`. - */ -#if (NPY_SIZEOF_BYTE == NPY_SIZEOF_SHORT) - PyByteArrType_Type.tp_name = "numpy.byte"; - PyUByteArrType_Type.tp_name = "numpy.ubyte"; -#endif -#if (NPY_SIZEOF_SHORT == NPY_SIZEOF_INT) - PyShortArrType_Type.tp_name = "numpy.short"; - PyUShortArrType_Type.tp_name = "numpy.ushort"; -#endif -#if (NPY_SIZEOF_INT == NPY_SIZEOF_LONG) - PyIntArrType_Type.tp_name = "numpy.intc"; - PyUIntArrType_Type.tp_name = "numpy.uintc"; -#endif -#if (NPY_SIZEOF_LONGLONG == NPY_SIZEOF_LONG) - PyLongLongArrType_Type.tp_name = "numpy.longlong"; - PyULongLongArrType_Type.tp_name = "numpy.ulonglong"; -#endif - - /* - Do the same for longdouble - */ -#if (NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE) - PyLongDoubleArrType_Type.tp_name = "numpy.longdouble"; - PyCLongDoubleArrType_Type.tp_name = "numpy.clongdouble"; -#endif } typedef struct { diff --git a/numpy/core/src/multiarray/usertypes.c b/numpy/core/src/multiarray/usertypes.c index a172343f1..16ca74ef8 100644 --- a/numpy/core/src/multiarray/usertypes.c +++ b/numpy/core/src/multiarray/usertypes.c @@ -261,12 +261,43 @@ PyArray_RegisterDataType(PyArray_Descr *descr) return -1; } + /* + * Legacy user DTypes classes cannot have a name, since the user never + * defined one. So we create a name for them here. These DTypes are + * effectively static types. + * + * Note: we have no intention of freeing the memory again since this + * behaves identically to static type definition. + */ + + const char *scalar_name = descr->typeobj->tp_name; + /* + * We have to take only the name, and ignore the module to get + * a reasonable __name__, since static types are limited in this regard + * (this is not ideal, but not a big issue in practice). + * This is what Python does to print __name__ for static types. + */ + const char *dot = strrchr(scalar_name, '.'); + if (dot) { + scalar_name = dot + 1; + } + Py_ssize_t name_length = strlen(scalar_name) + 14; + + char *name = PyMem_Malloc(name_length); + if (name == NULL) { + PyErr_NoMemory(); + return -1; + } + + snprintf(name, name_length, "numpy.dtype[%s]", scalar_name); + userdescrs[NPY_NUMUSERTYPES++] = descr; descr->type_num = typenum; - if (dtypemeta_wrap_legacy_descriptor(descr) < 0) { + if (dtypemeta_wrap_legacy_descriptor(descr, name, NULL) < 0) { descr->type_num = -1; NPY_NUMUSERTYPES--; + PyMem_Free(name); /* free the name only on failure */ return -1; } if (use_void_clearimpl) { diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index f764a4daa..57831f46f 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -7,6 +7,7 @@ import types from typing import Any import numpy as np +import numpy.dtypes from numpy.core._rational_tests import rational from numpy.core._multiarray_tests import create_custom_field_dtype from numpy.testing import ( @@ -1563,8 +1564,21 @@ class TestDTypeClasses: dtype = np.dtype(dtype) assert isinstance(dtype, np.dtype) assert type(dtype) is not np.dtype - assert type(dtype).__name__ == f"dtype[{dtype.type.__name__}]" - assert type(dtype).__module__ == "numpy" + if dtype.type.__name__ != "rational": + dt_name = type(dtype).__name__.lower().removesuffix("dtype") + if dt_name == "uint" or dt_name == "int": + # The scalar names has a `c` attached because "int" is Python + # int and that is long... + dt_name += "c" + sc_name = dtype.type.__name__ + assert dt_name == sc_name.strip("_") + assert type(dtype).__module__ == "numpy.dtypes" + + assert getattr(numpy.dtypes, type(dtype).__name__) is type(dtype) + else: + assert type(dtype).__name__ == "dtype[rational]" + assert type(dtype).__module__ == "numpy" + assert not type(dtype)._abstract # the flexible dtypes and datetime/timedelta have additional parameters @@ -1599,6 +1613,20 @@ class TestDTypeClasses: for code in non_numeric_codes: assert not type(np.dtype(code))._is_numeric + @pytest.mark.parametrize("int_", ["UInt", "Int"]) + @pytest.mark.parametrize("size", [8, 16, 32, 64]) + def test_integer_alias_names(self, int_, size): + DType = getattr(numpy.dtypes, f"{int_}{size}DType") + sctype = getattr(numpy, f"{int_.lower()}{size}") + assert DType.type is sctype + assert DType.__name__.lower().removesuffix("dtype") == sctype.__name__ + + @pytest.mark.parametrize("name", + ["Half", "Float", "Double", "CFloat", "CDouble"]) + def test_float_alias_names(self, name): + with pytest.raises(AttributeError): + getattr(numpy.dtypes, name + "DType") is numpy.dtypes.Float16DType + class TestFromCTypes: diff --git a/numpy/dtypes.py b/numpy/dtypes.py new file mode 100644 index 000000000..fd35caf97 --- /dev/null +++ b/numpy/dtypes.py @@ -0,0 +1,77 @@ +""" +DType classes and utility (:mod:`numpy.dtypes`) +=============================================== + +This module is home to specific dtypes related functionality and their classes. +For more general information about dtypes, also see `numpy.dtype` and +:ref:`arrays.dtypes`. + +Similar to the builtin ``types`` module, this submodule defines types (classes) +that are not widely used directly. + +.. versionadded:: NumPy 1.25 + + The dtypes module is new in NumPy 1.25. Previously DType classes were + only accessible indirectly. + + +DType classes +------------- + +The following are the classes of the corresponding NumPy dtype instances and +NumPy scalar types. The classes can be used in ``isinstance`` checks and can +also be instantiated or used directly. Direct use of these classes is not +typical, since their scalar counterparts (e.g. ``np.float64``) or strings +like ``"float64"` can be used. + +.. list-table:: + :header-rows: 1 + + * - Group + - DType class + + * - Boolean + - ``BoolDType`` + + * - Bit-sized integers + - ``Int8DType``, ``UInt8DType``, ``Int16DType``, ``UInt16DType``, + ``Int32DType``, ``UInt32DType``, ``Int64DType``, ``UInt64DType`` + + * - C-named integers (may be aliases) + - ``ByteDType``, ``UByteDType``, ``ShortDType``, ``UShortDType``, + ``IntDType``, ``UIntDType``, ``LongDType``, ``ULongDType``, + ``LongLongDType``, ``ULongLongDType`` + + * - Floating point + - ``Float16DType``, ``Float32DType``, ``Float64DType``, + ``LongDoubleDType`` + + * - Complex + - ``Complex64DType``, ``Complex128DType``, ``CLongDoubleDType`` + + * - Strings + - ``BytesDType``, ``BytesDType`` + + * - Times + - ``DateTime64DType``, ``TimeDelta64DType`` + + * - Others + - ``ObjectDType``, ``VoidDType`` + +""" + +__all__ = [] + + +def _add_dtype_helper(DType, alias): + # Function to add DTypes a bit more conveniently without channeling them + # through `numpy.core._multiarray_umath` namespace or similar. + from numpy import dtypes + + setattr(dtypes, DType.__name__, DType) + __all__.append(DType.__name__) + + if alias: + alias = alias.removeprefix("numpy.dtypes.") + setattr(dtypes, alias, DType) + __all__.append(alias) diff --git a/numpy/dtypes.pyi b/numpy/dtypes.pyi new file mode 100644 index 000000000..2f7e846f2 --- /dev/null +++ b/numpy/dtypes.pyi @@ -0,0 +1,43 @@ +import numpy as np + + +__all__: list[str] + +# Boolean: +BoolDType = np.dtype[np.bool_] +# Sized integers: +Int8DType = np.dtype[np.int8] +UInt8DType = np.dtype[np.uint8] +Int16DType = np.dtype[np.int16] +UInt16DType = np.dtype[np.uint16] +Int32DType = np.dtype[np.int32] +UInt32DType = np.dtype[np.uint32] +Int64DType = np.dtype[np.int64] +UInt64DType = np.dtype[np.uint64] +# Standard C-named version/alias: +ByteDType = np.dtype[np.byte] +UByteDType = np.dtype[np.ubyte] +ShortDType = np.dtype[np.short] +UShortDType = np.dtype[np.ushort] +IntDType = np.dtype[np.intc] +UIntDType = np.dtype[np.uintc] +LongDType = np.dtype[np.int_] # Unfortunately, the correct scalar +ULongDType = np.dtype[np.uint] # Unfortunately, the correct scalar +LongLongDType = np.dtype[np.longlong] +ULongLongDType = np.dtype[np.ulonglong] +# Floats +Float16DType = np.dtype[np.float16] +Float32DType = np.dtype[np.float32] +Float64DType = np.dtype[np.float64] +LongDoubleDType = np.dtype[np.longdouble] +# Complex: +Complex64DType = np.dtype[np.complex64] +Complex128DType = np.dtype[np.complex128] +CLongDoubleDType = np.dtype[np.clongdouble] +# Others: +ObjectDType = np.dtype[np.object_] +BytesDType = np.dtype[np.bytes_] +StrDType = np.dtype[np.str_] +VoidDType = np.dtype[np.void] +DateTime64DType = np.dtype[np.datetime64] +TimeDelta64DType = np.dtype[np.timedelta64] diff --git a/numpy/meson.build b/numpy/meson.build index 3c0adf6d0..de36e0d49 100644 --- a/numpy/meson.build +++ b/numpy/meson.build @@ -104,6 +104,8 @@ python_sources = [ 'ctypeslib.pyi', 'exceptions.py', 'exceptions.pyi', + 'dtypes.py', + 'dtypes.pyi', 'matlib.py', 'py.typed', 'version.py' diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index e0681426e..eaa89aa6f 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -136,6 +136,7 @@ PUBLIC_MODULES = ['numpy.' + s for s in [ "doc", "doc.constants", "doc.ufuncs", + "dtypes", "exceptions", "f2py", "fft", |
