summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2020-05-27 09:38:33 +0300
committerGitHub <noreply@github.com>2020-05-27 09:38:33 +0300
commit36e017194c32a53cf2965a513243cb3c348095df (patch)
tree1051393887e23e5895df03917a338b6bd7bd82c2 /numpy
parent74ad82c422f53dcf8637970a6f399adb37c1a67a (diff)
parent2ea745b41e093fa4c100a074be9392a1b44d1f6f (diff)
downloadnumpy-36e017194c32a53cf2965a513243cb3c348095df.tar.gz
Merge pull request #15508 from seberg/dtypemeta-new
API: Create Preliminary DTypeMeta class and np.dtype subclasses
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/code_generators/cversions.txt3
-rw-r--r--numpy/core/code_generators/genapi.py21
-rw-r--r--numpy/core/code_generators/generate_numpy_api.py4
-rw-r--r--numpy/core/code_generators/numpy_api.py4
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h71
-rw-r--r--numpy/core/setup.py2
-rw-r--r--numpy/core/setup_common.py3
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src12
-rw-r--r--numpy/core/src/multiarray/descriptor.c59
-rw-r--r--numpy/core/src/multiarray/dtypemeta.c269
-rw-r--r--numpy/core/src/multiarray/dtypemeta.h7
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c16
-rw-r--r--numpy/core/src/multiarray/usertypes.c6
-rw-r--r--numpy/core/tests/test_dtype.py34
14 files changed, 481 insertions, 30 deletions
diff --git a/numpy/core/code_generators/cversions.txt b/numpy/core/code_generators/cversions.txt
index 528113a9e..1868610f4 100644
--- a/numpy/core/code_generators/cversions.txt
+++ b/numpy/core/code_generators/cversions.txt
@@ -52,3 +52,6 @@
# Version 13 (NumPy 1.19) No change.
# Version 13 (NumPy 1.20) No change.
0x0000000d = 5b0e8bbded00b166125974fc71e80a33
+
+# Version 14 (NumPy 1.19) DType related API additions
+0x0000000e = 17a0f366e55ec05e5c5c149123478452
diff --git a/numpy/core/code_generators/genapi.py b/numpy/core/code_generators/genapi.py
index 88dc2d90a..d88772bdc 100644
--- a/numpy/core/code_generators/genapi.py
+++ b/numpy/core/code_generators/genapi.py
@@ -37,6 +37,7 @@ API_FILES = [join('multiarray', 'alloc.c'),
join('multiarray', 'datetime_busdaycal.c'),
join('multiarray', 'datetime_strings.c'),
join('multiarray', 'descriptor.c'),
+ join('multiarray', 'dtypemeta.c'),
join('multiarray', 'einsum.c.src'),
join('multiarray', 'flagsobject.c'),
join('multiarray', 'getset.c'),
@@ -309,11 +310,13 @@ def write_file(filename, data):
# Those *Api classes instances know how to output strings for the generated code
class TypeApi:
- def __init__(self, name, index, ptr_cast, api_name):
+ def __init__(self, name, index, ptr_cast, api_name, internal_type=None):
self.index = index
self.name = name
self.ptr_cast = ptr_cast
self.api_name = api_name
+ # The type used internally, if None, same as exported (ptr_cast)
+ self.internal_type = internal_type
def define_from_array_api_string(self):
return "#define %s (*(%s *)%s[%d])" % (self.name,
@@ -325,9 +328,19 @@ class TypeApi:
return " (void *) &%s" % self.name
def internal_define(self):
- astr = """\
-extern NPY_NO_EXPORT PyTypeObject %(type)s;
-""" % {'type': self.name}
+ if self.internal_type is None:
+ return f"extern NPY_NO_EXPORT {self.ptr_cast} {self.name};\n"
+
+ # If we are here, we need to define a larger struct internally, which
+ # the type can be cast safely. But we want to normally use the original
+ # type, so name mangle:
+ mangled_name = f"{self.name}Full"
+ astr = (
+ # Create the mangled name:
+ f"extern NPY_NO_EXPORT {self.internal_type} {mangled_name};\n"
+ # And define the name as: (*(type *)(&mangled_name))
+ f"#define {self.name} (*({self.ptr_cast} *)(&{mangled_name}))\n"
+ )
return astr
class GlobalVarApi:
diff --git a/numpy/core/code_generators/generate_numpy_api.py b/numpy/core/code_generators/generate_numpy_api.py
index fe21bc543..7997135bb 100644
--- a/numpy/core/code_generators/generate_numpy_api.py
+++ b/numpy/core/code_generators/generate_numpy_api.py
@@ -201,7 +201,9 @@ def do_generate_api(targets, sources):
for name, val in types_api.items():
index = val[0]
- multiarray_api_dict[name] = TypeApi(name, index, 'PyTypeObject', api_name)
+ internal_type = None if len(val) == 1 else val[1]
+ multiarray_api_dict[name] = TypeApi(
+ name, index, 'PyTypeObject', api_name, internal_type)
if len(multiarray_api_dict) != len(multiarray_api_index):
keys_dict = set(multiarray_api_dict.keys())
diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py
index 916fb537e..fbd323368 100644
--- a/numpy/core/code_generators/numpy_api.py
+++ b/numpy/core/code_generators/numpy_api.py
@@ -30,7 +30,9 @@ multiarray_scalar_bool_values = {
multiarray_types_api = {
'PyBigArray_Type': (1,),
'PyArray_Type': (2,),
- 'PyArrayDescr_Type': (3,),
+ # Internally, PyArrayDescr_Type is a PyArray_DTypeMeta,
+ # the following also defines PyArrayDescr_TypeFull (Full appended)
+ 'PyArrayDescr_Type': (3, "PyArray_DTypeMeta"),
'PyArrayFlags_Type': (4,),
'PyArrayIter_Type': (5,),
'PyArrayMultiIter_Type': (6,),
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 5b7e8952e..5dd62e64a 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -1809,6 +1809,77 @@ typedef struct {
typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
void *user_data);
+
+/*
+ * PyArray_DTypeMeta related definitions.
+ *
+ * As of now, this API is preliminary and will be extended as necessary.
+ */
+#if defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD
+ /*
+ * The Structures defined in this block are considered private API and
+ * may change without warning!
+ */
+ /* TODO: Make this definition public in the API, as soon as its settled */
+ NPY_NO_EXPORT PyTypeObject PyArrayDTypeMeta_Type;
+
+ /*
+ * While NumPy DTypes would not need to be heap types the plan is to
+ * make DTypes available in Python at which point we will probably want
+ * them to be.
+ * Since we also wish to add fields to the DType class, this looks like
+ * a typical instance definition, but with PyHeapTypeObject instead of
+ * only the PyObject_HEAD.
+ * This must only be exposed very extremely careful consideration, since
+ * it is a fairly complex construct which may be better to allow
+ * refactoring of.
+ */
+ typedef struct _PyArray_DTypeMeta {
+ PyHeapTypeObject super;
+
+ /*
+ * Most DTypes will have a singleton default instance, for the
+ * parametric legacy DTypes (bytes, string, void, datetime) this
+ * may be a pointer to the *prototype* instance?
+ */
+ PyArray_Descr *singleton;
+ /*
+ * Is this DType created using the old API? This exists mainly to
+ * allow for assertions in paths specific to wrapping legacy types.
+ */
+ npy_bool legacy;
+ /* The values stored by a parametric datatype depend on its instance */
+ npy_bool parametric;
+ /* whether the DType can be instantiated (i.e. np.dtype cannot) */
+ npy_bool abstract;
+
+ /*
+ * The following fields replicate the most important dtype information.
+ * In the legacy implementation most of these are stored in the
+ * PyArray_Descr struct.
+ */
+ /* The type object of the scalar instances (may be NULL?) */
+ PyTypeObject *scalar_type;
+ /* kind for this type */
+ char kind;
+ /* unique-character representing this type */
+ char type;
+ /* flags describing data type */
+ char flags;
+ /* number representing this type */
+ int type_num;
+ /*
+ * Point to the original ArrFuncs.
+ * NOTE: We could make a copy to detect changes to `f`.
+ */
+ PyArray_ArrFuncs *f;
+ } PyArray_DTypeMeta;
+
+ #define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr))
+
+#endif /* NPY_INTERNAL_BUILD */
+
+
/*
* Use the keyword NPY_DEPRECATED_INCLUDES to ensure that the header files
* npy_*_*_deprecated_api.h are only included from here and nowhere else.
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
index 9de9fc632..16bac4272 100644
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -784,6 +784,7 @@ def configuration(parent_package='',top_path=None):
join('src', 'multiarray', 'conversion_utils.h'),
join('src', 'multiarray', 'ctors.h'),
join('src', 'multiarray', 'descriptor.h'),
+ join('src', 'multiarray', 'dtypemeta.h'),
join('src', 'multiarray', 'dragon4.h'),
join('src', 'multiarray', 'getset.h'),
join('src', 'multiarray', 'hashdescr.h'),
@@ -842,6 +843,7 @@ def configuration(parent_package='',top_path=None):
join('src', 'multiarray', 'datetime_busday.c'),
join('src', 'multiarray', 'datetime_busdaycal.c'),
join('src', 'multiarray', 'descriptor.c'),
+ join('src', 'multiarray', 'dtypemeta.c'),
join('src', 'multiarray', 'dragon4.c'),
join('src', 'multiarray', 'dtype_transfer.c'),
join('src', 'multiarray', 'einsum.c.src'),
diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py
index 63c4a76a9..72b59f9ae 100644
--- a/numpy/core/setup_common.py
+++ b/numpy/core/setup_common.py
@@ -40,7 +40,8 @@ C_ABI_VERSION = 0x01000009
# 0x0000000c - 1.14.x
# 0x0000000c - 1.15.x
# 0x0000000d - 1.16.x
-C_API_VERSION = 0x0000000d
+# 0x0000000e - 1.19.x
+C_API_VERSION = 0x0000000e
class MismatchCAPIWarning(Warning):
pass
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index 38d5f21eb..552c56349 100644
--- a/numpy/core/src/multiarray/arraytypes.c.src
+++ b/numpy/core/src/multiarray/arraytypes.c.src
@@ -20,6 +20,7 @@
#include "npy_sort.h"
#include "common.h"
#include "ctors.h"
+#include "dtypemeta.h"
#include "lowlevel_strided_loops.h"
#include "usertypes.h"
#include "_datetime.h"
@@ -4367,6 +4368,17 @@ set_typeinfo(PyObject *dict)
PyObject *cobj, *key;
/*
+ * Override the base class for all types, eventually all of this logic
+ * 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;
+ }
+ }
+
+ /*
* Add cast functions for the new types
*/
diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c
index b26a26abf..b4107f8f3 100644
--- a/numpy/core/src/multiarray/descriptor.c
+++ b/numpy/core/src/multiarray/descriptor.c
@@ -1744,7 +1744,7 @@ fail:
NPY_NO_EXPORT PyArray_Descr *
PyArray_DescrNew(PyArray_Descr *base)
{
- PyArray_Descr *newdescr = PyObject_New(PyArray_Descr, &PyArrayDescr_Type);
+ PyArray_Descr *newdescr = PyObject_New(PyArray_Descr, Py_TYPE(base));
if (newdescr == NULL) {
return NULL;
@@ -2261,9 +2261,16 @@ static PyGetSetDef arraydescr_getsets[] = {
};
static PyObject *
-arraydescr_new(PyTypeObject *NPY_UNUSED(subtype),
+arraydescr_new(PyTypeObject *subtype,
PyObject *args, PyObject *kwds)
{
+ if (subtype != &PyArrayDescr_Type) {
+ /* The DTypeMeta class should prevent this from happening. */
+ PyErr_Format(PyExc_SystemError,
+ "'%S' must not inherit np.dtype.__new__().", subtype);
+ return NULL;
+ }
+
PyObject *odescr, *metadata=NULL;
PyArray_Descr *descr, *conv;
npy_bool align = NPY_FALSE;
@@ -2334,6 +2341,7 @@ arraydescr_new(PyTypeObject *NPY_UNUSED(subtype),
return (PyObject *)conv;
}
+
/*
* Return a tuple of
* (cleaned metadata dictionary, tuple with (str, num))
@@ -3456,21 +3464,34 @@ static PyMappingMethods descr_as_mapping = {
/****************** End of Mapping Protocol ******************************/
-NPY_NO_EXPORT PyTypeObject PyArrayDescr_Type = {
- PyVarObject_HEAD_INIT(NULL, 0)
- .tp_name = "numpy.dtype",
- .tp_basicsize = sizeof(PyArray_Descr),
- /* methods */
- .tp_dealloc = (destructor)arraydescr_dealloc,
- .tp_repr = (reprfunc)arraydescr_repr,
- .tp_as_number = &descr_as_number,
- .tp_as_sequence = &descr_as_sequence,
- .tp_as_mapping = &descr_as_mapping,
- .tp_str = (reprfunc)arraydescr_str,
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_richcompare = (richcmpfunc)arraydescr_richcompare,
- .tp_methods = arraydescr_methods,
- .tp_members = arraydescr_members,
- .tp_getset = arraydescr_getsets,
- .tp_new = arraydescr_new,
+
+/*
+ * NOTE: Since this is a MetaClass, the name has Full appended here, the
+ * correct name of the type is PyArrayDescr_Type.
+ */
+NPY_NO_EXPORT PyArray_DTypeMeta PyArrayDescr_TypeFull = {
+ {{
+ /* NULL represents `type`, this is set to DTypeMeta at import time */
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "numpy.dtype",
+ .tp_basicsize = sizeof(PyArray_Descr),
+ .tp_dealloc = (destructor)arraydescr_dealloc,
+ .tp_repr = (reprfunc)arraydescr_repr,
+ .tp_as_number = &descr_as_number,
+ .tp_as_sequence = &descr_as_sequence,
+ .tp_as_mapping = &descr_as_mapping,
+ .tp_str = (reprfunc)arraydescr_str,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_richcompare = (richcmpfunc)arraydescr_richcompare,
+ .tp_methods = arraydescr_methods,
+ .tp_members = arraydescr_members,
+ .tp_getset = arraydescr_getsets,
+ .tp_new = arraydescr_new,
+ },},
+ .type_num = -1,
+ .kind = '\0',
+ .abstract = 1,
+ .parametric = 0,
+ .singleton = 0,
+ .scalar_type = NULL,
};
diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c
new file mode 100644
index 000000000..76f7b599a
--- /dev/null
+++ b/numpy/core/src/multiarray/dtypemeta.c
@@ -0,0 +1,269 @@
+/* Array Descr Object */
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include "structmember.h"
+#include "assert.h"
+
+#define NPY_NO_DEPRECATED_API NPY_API_VERSION
+#define _MULTIARRAYMODULE
+#include <numpy/ndarraytypes.h>
+#include "npy_pycompat.h"
+
+#include "dtypemeta.h"
+
+
+static void
+dtypemeta_dealloc(PyArray_DTypeMeta *self) {
+ /* Do not accidentally delete a statically defined DType: */
+ assert(((PyTypeObject *)self)->tp_flags & Py_TPFLAGS_HEAPTYPE);
+
+ Py_XDECREF(self->scalar_type);
+ Py_XDECREF(self->singleton);
+ PyType_Type.tp_dealloc((PyObject *) self);
+}
+
+static PyObject *
+dtypemeta_new(PyTypeObject *NPY_UNUSED(type),
+ PyObject *NPY_UNUSED(args), PyObject *NPY_UNUSED(kwds))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "Preliminary-API: Cannot subclass DType.");
+ return NULL;
+}
+
+static int
+dtypemeta_init(PyTypeObject *NPY_UNUSED(type),
+ PyObject *NPY_UNUSED(args), PyObject *NPY_UNUSED(kwds))
+{
+ PyErr_SetString(PyExc_TypeError,
+ "Preliminary-API: Cannot __init__ DType class.");
+ return -1;
+}
+
+/**
+ * tp_is_gc slot of Python types. This is implemented only for documentation
+ * purposes to indicate and document the subtleties involved.
+ *
+ * Python Type objects are either statically created (typical C-Extension type)
+ * or HeapTypes (typically created in Python).
+ * HeapTypes have the Py_TPFLAGS_HEAPTYPE flag and are garbage collected.
+ * Our DTypeMeta instances (`np.dtype` and its subclasses) *may* be HeapTypes
+ * if the Py_TPFLAGS_HEAPTYPE flag is set (they are created from Python).
+ * They are not for legacy DTypes or np.dtype itself.
+ *
+ * @param self
+ * @return nonzero if the object is garbage collected
+ */
+static NPY_INLINE int
+dtypemeta_is_gc(PyObject *dtype_class)
+{
+ return PyType_Type.tp_is_gc(dtype_class);
+}
+
+
+static int
+dtypemeta_traverse(PyArray_DTypeMeta *type, visitproc visit, void *arg)
+{
+ /*
+ * We have to traverse the base class (if it is a HeapType).
+ * PyType_Type will handle this logic for us.
+ * This function is currently not used, but will probably be necessary
+ * in the future when we implement HeapTypes (python/dynamically
+ * defined types). It should be revised at that time.
+ */
+ assert(0);
+ assert(!type->legacy && (PyTypeObject *)type != &PyArrayDescr_Type);
+ Py_VISIT(type->singleton);
+ Py_VISIT(type->scalar_type);
+ return PyType_Type.tp_traverse((PyObject *)type, visit, arg);
+}
+
+
+static PyObject *
+legacy_dtype_default_new(PyArray_DTypeMeta *self,
+ PyObject *args, PyObject *kwargs)
+{
+ /* TODO: This should allow endianess and possibly metadata */
+ if (self->parametric) {
+ /* reject parametric ones since we would need to get unit, etc. info */
+ PyErr_Format(PyExc_TypeError,
+ "Preliminary-API: Flexible/Parametric legacy DType '%S' can "
+ "only be instantiated using `np.dtype(...)`", self);
+ return NULL;
+ }
+
+ if (PyTuple_GET_SIZE(args) != 0 ||
+ (kwargs != NULL && PyDict_Size(kwargs))) {
+ PyErr_Format(PyExc_TypeError,
+ "currently only the no-argument instantiation is supported; "
+ "use `np.dtype` instead.");
+ return NULL;
+ }
+ Py_INCREF(self->singleton);
+ return (PyObject *)self->singleton;
+}
+
+/**
+ * This function takes a PyArray_Descr and replaces its base class with
+ * a newly created dtype subclass (DTypeMeta instances).
+ * There are some subtleties that need to be remembered when doing this,
+ * first for the class objects itself it could be either a HeapType or not.
+ * Since we are defining the DType from C, we will not make it a HeapType,
+ * thus making it identical to a typical *static* type (except that we
+ * malloc it). We could do it the other way, but there seems no reason to
+ * do so.
+ *
+ * The DType instances (the actual dtypes or descriptors), are based on
+ * prototypes which are passed in. These should not be garbage collected
+ * and thus Py_TPFLAGS_HAVE_GC is not set. (We could allow this, but than
+ * would have to allocate a new object, since the GC needs information before
+ * the actual struct).
+ *
+ * The above is the reason why we should works exactly like we would for a
+ * static type here.
+ * Otherwise, we blurry the lines between C-defined extension classes
+ * and Python subclasses. e.g. `class MyInt(int): pass` is very different
+ * from our `class Float64(np.dtype): pass`, because the latter should not
+ * 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.
+ *
+ * @returns 0 on success, -1 on failure.
+ */
+NPY_NO_EXPORT int
+dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
+{
+ if (Py_TYPE(descr) != &PyArrayDescr_Type) {
+ PyErr_Format(PyExc_RuntimeError,
+ "During creation/wrapping of legacy DType, the original class "
+ "was not PyArrayDescr_Type (it is replaced in this step).");
+ 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;
+ }
+ ssize_t name_length = strlen(scalar_name) + 14;
+
+ char *tp_name = malloc(name_length);
+ if (tp_name == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ snprintf(tp_name, name_length, "numpy.dtype[%s]", scalar_name);
+
+ PyArray_DTypeMeta *dtype_class = malloc(sizeof(PyArray_DTypeMeta));
+ if (dtype_class == NULL) {
+ PyDataMem_FREE(tp_name);
+ return -1;
+ }
+ /*
+ * Initialize the struct fields identically to static code by copying
+ * a prototype instances for everything except our own fields which
+ * vary between the DTypes.
+ * In particular any Object initialization must be strictly copied from
+ * the untouched prototype to avoid complexities (e.g. with PyPy).
+ * Any Type slots need to be fixed before PyType_Ready, although most
+ * will be inherited automatically there.
+ */
+ static PyArray_DTypeMeta prototype = {
+ {{
+ PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0)
+ .tp_name = NULL, /* set below */
+ .tp_basicsize = sizeof(PyArray_Descr),
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_base = &PyArrayDescr_Type,
+ .tp_new = (newfunc)legacy_dtype_default_new,
+ },},
+ .legacy = 1,
+ .abstract = 0, /* this is a concrete DType */
+ /* 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;
+
+ /* Let python finish the initialization (probably unnecessary) */
+ if (PyType_Ready((PyTypeObject *)dtype_class) < 0) {
+ return -1;
+ }
+
+ /*
+ * Fill DTypeMeta information that varies between DTypes, any variable
+ * type information would need to be set before PyType_Ready().
+ */
+ dtype_class->singleton = descr;
+ Py_INCREF(descr->typeobj);
+ dtype_class->scalar_type = descr->typeobj;
+ dtype_class->type_num = descr->type_num;
+ dtype_class->type = descr->type;
+ dtype_class->f = descr->f;
+ dtype_class->kind = descr->kind;
+
+ if (PyTypeNum_ISDATETIME(descr->type_num)) {
+ /* Datetimes are flexible, but were not considered previously */
+ dtype_class->parametric = NPY_TRUE;
+ }
+ else if (PyTypeNum_ISFLEXIBLE(descr->type_num)) {
+ dtype_class->parametric = NPY_TRUE;
+ }
+
+ /* Finally, replace the current class of the descr */
+ Py_TYPE(descr) = (PyTypeObject *)dtype_class;
+
+ return 0;
+}
+
+
+/*
+ * Simple exposed information, defined for each DType (class). This is
+ * preliminary (the flags should also return bools).
+ */
+static PyMemberDef dtypemeta_members[] = {
+ {"_abstract",
+ T_BYTE, offsetof(PyArray_DTypeMeta, abstract), READONLY, NULL},
+ {"type",
+ T_OBJECT, offsetof(PyArray_DTypeMeta, scalar_type), READONLY, NULL},
+ {"_parametric",
+ T_BYTE, offsetof(PyArray_DTypeMeta, parametric), READONLY, NULL},
+ {NULL, 0, 0, 0, NULL},
+};
+
+
+NPY_NO_EXPORT PyTypeObject PyArrayDTypeMeta_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "numpy._DTypeMeta",
+ .tp_basicsize = sizeof(PyArray_DTypeMeta),
+ .tp_dealloc = (destructor)dtypemeta_dealloc,
+ /* Types are garbage collected (see dtypemeta_is_gc documentation) */
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
+ .tp_doc = "Preliminary NumPy API: The Type of NumPy DTypes (metaclass)",
+ .tp_members = dtypemeta_members,
+ .tp_base = NULL, /* set to PyType_Type at import time */
+ .tp_init = (initproc)dtypemeta_init,
+ .tp_new = dtypemeta_new,
+ .tp_is_gc = dtypemeta_is_gc,
+ .tp_traverse = (traverseproc)dtypemeta_traverse,
+};
+
diff --git a/numpy/core/src/multiarray/dtypemeta.h b/numpy/core/src/multiarray/dtypemeta.h
new file mode 100644
index 000000000..97152d1ad
--- /dev/null
+++ b/numpy/core/src/multiarray/dtypemeta.h
@@ -0,0 +1,7 @@
+#ifndef _NPY_DTYPEMETA_H
+#define _NPY_DTYPEMETA_H
+
+NPY_NO_EXPORT int
+dtypemeta_wrap_legacy_descriptor(PyArray_Descr *dtypem);
+
+#endif /*_NPY_DTYPEMETA_H */
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index a9f673d93..ab5076711 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -4446,6 +4446,18 @@ PyMODINIT_FUNC PyInit__multiarray_umath(void) {
if (set_matmul_flags(d) < 0) {
goto err;
}
+
+ PyArrayDTypeMeta_Type.tp_base = &PyType_Type;
+ if (PyType_Ready(&PyArrayDTypeMeta_Type) < 0) {
+ goto err;
+ }
+
+ PyArrayDescr_Type.tp_hash = PyArray_DescrHash;
+ Py_TYPE(&PyArrayDescr_Type) = &PyArrayDTypeMeta_Type;
+ if (PyType_Ready(&PyArrayDescr_Type) < 0) {
+ goto err;
+ }
+
initialize_casting_tables();
initialize_numeric_types();
if (initscalarmath(m) < 0) {
@@ -4479,10 +4491,6 @@ PyMODINIT_FUNC PyInit__multiarray_umath(void) {
goto err;
}
- PyArrayDescr_Type.tp_hash = PyArray_DescrHash;
- if (PyType_Ready(&PyArrayDescr_Type) < 0) {
- goto err;
- }
if (PyType_Ready(&PyArrayFlags_Type) < 0) {
goto err;
}
diff --git a/numpy/core/src/multiarray/usertypes.c b/numpy/core/src/multiarray/usertypes.c
index 997467b4d..bc320138d 100644
--- a/numpy/core/src/multiarray/usertypes.c
+++ b/numpy/core/src/multiarray/usertypes.c
@@ -37,6 +37,7 @@ maintainer email: oliphant.travis@ieee.org
#include "npy_pycompat.h"
#include "usertypes.h"
+#include "dtypemeta.h"
NPY_NO_EXPORT PyArray_Descr **userdescrs=NULL;
@@ -226,6 +227,11 @@ PyArray_RegisterDataType(PyArray_Descr *descr)
return -1;
}
userdescrs[NPY_NUMUSERTYPES++] = descr;
+
+ if (dtypemeta_wrap_legacy_descriptor(descr) < 0) {
+ return -1;
+ }
+
return typenum;
}
diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py
index c9a65cd9c..73aa01de6 100644
--- a/numpy/core/tests/test_dtype.py
+++ b/numpy/core/tests/test_dtype.py
@@ -1091,6 +1091,40 @@ class TestFromDTypeAttribute:
with pytest.raises(RecursionError):
np.dtype(dt(1))
+
+class TestDTypeClasses:
+ @pytest.mark.parametrize("dtype", list(np.typecodes['All']) + [rational])
+ def test_basic_dtypes_subclass_properties(self, dtype):
+ # Note: Except for the isinstance and type checks, these attributes
+ # are considered currently private and may change.
+ 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"
+ assert not type(dtype)._abstract
+
+ # the flexible dtypes and datetime/timedelta have additional parameters
+ # which are more than just storage information, these would need to be
+ # given when creating a dtype:
+ parametric = (np.void, np.str_, np.bytes_, np.datetime64, np.timedelta64)
+ if dtype.type not in parametric:
+ assert not type(dtype)._parametric
+ assert type(dtype)() is dtype
+ else:
+ assert type(dtype)._parametric
+ with assert_raises(TypeError):
+ type(dtype)()
+
+ def test_dtype_superclass(self):
+ assert type(np.dtype) is not type
+ assert isinstance(np.dtype, type)
+
+ assert type(np.dtype).__name__ == "_DTypeMeta"
+ assert type(np.dtype).__module__ == "numpy"
+ assert np.dtype._abstract
+
+
class TestFromCTypes:
@staticmethod