-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
MAINT: Move ctype -> dtype conversion to python #12254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
""" | ||
Conversion from ctypes to dtype. | ||
In an ideal world, we could acheive this through the PEP3118 buffer protocol, | ||
something like:: | ||
def dtype_from_ctypes_type(t): | ||
# needed to ensure that the shape of `t` is within memoryview.format | ||
class DummyStruct(ctypes.Structure): | ||
_fields_ = [('a', t)] | ||
# empty to avoid memory allocation | ||
ctype_0 = (DummyStruct * 0)() | ||
mv = memoryview(ctype_0) | ||
# convert the struct, and slice back out the field | ||
return _dtype_from_pep3118(mv.format)['a'] | ||
Unfortunately, this fails because: | ||
* ctypes cannot handle length-0 arrays with PEP3118 (bpo-32782) | ||
* PEP3118 cannot represent unions, but both numpy and ctypes can | ||
* ctypes cannot handle big-endian structs with PEP3118 (bpo-32780) | ||
""" | ||
import _ctypes | ||
import ctypes | ||
|
||
import numpy as np | ||
|
||
|
||
def _from_ctypes_array(t): | ||
return np.dtype((dtype_from_ctypes_type(t._type_), (t._length_,))) | ||
|
||
|
||
def _from_ctypes_structure(t): | ||
# TODO: gh-10533, gh-10532 | ||
fields = [] | ||
for item in t._fields_: | ||
if len(item) > 2: | ||
raise TypeError( | ||
"ctypes bitfields have no dtype equivalent") | ||
fname, ftyp = item | ||
fields.append((fname, dtype_from_ctypes_type(ftyp))) | ||
|
||
# by default, ctypes structs are aligned | ||
return np.dtype(fields, align=True) | ||
|
||
|
||
def dtype_from_ctypes_type(t): | ||
""" | ||
Construct a dtype object from a ctypes type | ||
""" | ||
if issubclass(t, _ctypes.Array): | ||
return _from_ctypes_array(t) | ||
elif issubclass(t, _ctypes._Pointer): | ||
raise TypeError("ctypes pointers have no dtype equivalent") | ||
elif issubclass(t, _ctypes.Structure): | ||
return _from_ctypes_structure(t) | ||
elif issubclass(t, _ctypes.Union): | ||
# TODO | ||
raise NotImplementedError( | ||
"conversion from ctypes.Union types like {} to dtype" | ||
.format(t.__name__)) | ||
elif isinstance(t._type_, str): | ||
return np.dtype(t._type_) | ||
else: | ||
raise NotImplementedError( | ||
"Unknown ctypes type {}".format(t.__name__)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -796,13 +796,13 @@ def _ufunc_doc_signature_formatter(ufunc): | |
) | ||
|
||
|
||
def _is_from_ctypes(obj): | ||
# determine if an object comes from ctypes, in order to work around | ||
def npy_ctypes_check(cls): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I see that the static type is checked in the C code. |
||
# determine if a class comes from ctypes, in order to work around | ||
# a bug in the buffer protocol for those objects, bpo-10746 | ||
try: | ||
# ctypes class are new-st 8000 yle, so have an __mro__. This probably fails | ||
# for ctypes classes with multiple inheritance. | ||
ctype_base = type(obj).__mro__[-2] | ||
ctype_base = cls.__mro__[-2] | ||
# right now, they're part of the _ctypes module | ||
return 'ctypes' in ctype_base.__module__ | ||
except Exception: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
#ifndef NPY_CTYPES_H | ||
#define NPY_CTYPES_H | ||
|
||
#include <Python.h> | ||
|
||
#include "npy_import.h" | ||
|
||
/* | ||
* Check if a python type is a ctypes class. | ||
* | ||
* Works like the Py<type>_Check functions, returning true if the argument | ||
* looks like a ctypes object. | ||
* | ||
* This entire function is just a wrapper around the Python function of the | ||
* same name. | ||
*/ | ||
NPY_INLINE static int | ||
npy_ctypes_check(PyTypeObject *obj) | ||
{ | ||
static PyObject *py_func = NULL; | ||
PyObject *ret_obj; | ||
int ret; | ||
|
||
npy_cache_import("numpy.core._internal", "npy_ctypes_check", &py_func); | ||
if (py_func == NULL) { | ||
goto fail; | ||
} | ||
|
||
ret_obj = PyObject_CallFunctionObjArgs(py_func, (PyObject *)obj, NULL); | ||
if (ret_obj == NULL) { | ||
goto fail; | ||
} | ||
|
||
ret = PyObject_IsTrue(ret_obj); | ||
if (ret == -1) { | ||
goto fail; | ||
} | ||
|
||
return ret; | ||
|
||
fail: | ||
/* If the above fails, then we should just assume that the type is not from | ||
* ctypes | ||
*/ | ||
PyErr_Clear(); | ||
return 0; | ||
} | ||
|
||
#endif | ||
mattip marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
|
||
#include "npy_config.h" | ||
|
||
#include "npy_import.h" | ||
#include "npy_ctypes.h" | ||
#include "npy_pycompat.h" | ||
#include "multiarraymodule.h" | ||
|
||
9E88
|
@@ -1381,15 +1381,7 @@ _array_from_buffer_3118(PyObject *memoryview) | |
* Note that even if the above are fixed in master, we have to drop the | ||
* early patch versions of python to actually make use of the fixes. | ||
*/ | ||
|
||
int is_ctypes = _is_from_ctypes(view->obj); | ||
if (is_ctypes < 0) { | ||
/* This error is not useful */ | ||
PyErr_WriteUnraisable(view->obj); | ||
is_ctypes = 0; | ||
} | ||
|
||
if (!is_ctypes) { | ||
if (!npy_ctypes_check(Py_TYPE(view->obj))) { | ||
/* This object has no excuse for a broken PEP3118 buffer */ | ||
PyErr_Format( | ||
PyExc_RuntimeError, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
#include "numpy/arrayscalars.h" | ||
|
||
#include "npy_config.h" | ||
|
||
#include "npy_ctypes.h" | ||
#include "npy_pycompat.h" | ||
|
||
#include "_datetime.h" | ||
|
@@ -54,79 +54,46 @@ Borrowed_PyMapping_GetItemString(PyObject *o, char *key) | |
return ret; | ||
} | ||
|
||
/* | ||
* Creates a dtype object from ctypes inputs. | ||
* | ||
* Returns a new reference to a dtype object, or NULL | ||
* if this is not possible. When it returns NULL, it does | ||
* not set a Python exception. | ||
*/ | ||
static PyArray_Descr * | ||
_arraydescr_fromctypes(PyObject *obj) | ||
_arraydescr_from_ctypes_type(PyTypeObject *type) | ||
{ | ||
PyObject *dtypedescr; | ||
PyArray_Descr *newdescr; | ||
int ret; | ||
PyObject *_numpy_dtype_ctypes; | ||
PyObject *res; | ||
|
||
/* Understand basic ctypes */ | ||
dtypedescr = PyObject_GetAttrString(obj, "_type_"); | ||
PyErr_Clear(); | ||
if (dtypedescr) { | ||
ret = PyArray_DescrConverter(dtypedescr, &newdescr); | ||
Py_DECREF(dtypedescr); | ||
if (ret == NPY_SUCCEED) { | ||
PyObject *length; | ||
/* Check for ctypes arrays */ | ||
length = PyObject_GetAttrString(obj, "_length_"); | ||
PyErr_Clear(); | ||
if (length) { | ||
/* derived type */ | ||
PyObject *newtup; | ||
PyArray_Descr *derived; | ||
newtup = Py_BuildValue("N(N)", newdescr, length); | ||
ret = PyArray_DescrConverter(newtup, &derived); | ||
Py_DECREF(newtup); | ||
if (ret == NPY_SUCCEED) { | ||
return derived; | ||
} | ||
PyErr_Clear(); | ||
return NULL; | ||
} | ||
return newdescr; | ||
} | ||
PyErr_Clear(); | ||
/* Call the python function of the same name. */ | ||
_numpy_dtype_ctypes = PyImport_ImportModule("numpy.core._dtype_ctypes"); | ||
eric-wieser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (_numpy_dtype_ctypes == NULL) { | ||
return NULL; | ||
} | ||
/* Understand ctypes structures -- | ||
bit-fields are not supported | ||
automatically aligns */ | ||
dtypedescr = PyObject_GetAttrString(obj, "_fields_"); | ||
PyErr_Clear(); | ||
if (dtypedescr) { | ||
ret = PyArray_DescrAlignConverter(dtypedescr, &newdescr); | ||
Py_DECREF(dtypedescr); | ||
if (ret == NPY_SUCCEED) { | ||
return newdescr; | ||
} | ||
PyErr_Clear(); | ||
res = PyObject_CallMethod(_numpy_dtype_ctypes, "dtype_from_ctypes_type", "O", (PyObject *)type); | ||
Py_DECREF(_numpy_dtype_ctypes); | ||
if (res == NULL) { | ||
return NULL; | ||
} | ||
|
||
return NULL; | ||
/* | ||
* sanity check that dtype_from_ctypes_type returned the right type, | ||
* since getting it wrong would give segfaults. | ||
*/ | ||
if (!PyObject_TypeCheck(res, &PyArrayDescr_Type)) { | ||
Py_DECREF(res); | ||
PyErr_BadInternalCall(); | ||
return NULL; | ||
} | ||
|
||
return (PyArray_Descr *)res; | ||
} | ||
|
||
/* | ||
* This function creates a dtype object when: | ||
* - The object has a "dtype" attribute, and it can be converted | ||
* to a dtype object. | ||
* - The object is a ctypes type object, including array | ||
* and structure types. | ||
* This function creates a dtype object when the object has a "dtype" attribute, | ||
* and it can be converted to a dtype object. | ||
* | ||
* Returns a new reference to a dtype object, or NULL | ||
* if this is not possible. When it returns NULL, it does | ||
* not set a Python exception. | ||
*/ | ||
NPY_NO_EXPORT PyArray_Descr * | ||
_arraydescr_fromobj(PyObject *obj) | ||
_arraydescr_from_dtype_attr(PyObject *obj) | ||
{ | ||
PyObject *dtypedescr; | ||
PyArray_Descr *newdescr = NULL; | ||
|
@@ -135,15 +102,18 @@ _arraydescr_fromobj(PyObject *obj) | |
/* For arbitrary objects that have a "dtype" attribute */ | ||
dtypedescr = PyObject_GetAttrString(obj, "dtype"); | ||
PyErr_Clear(); | ||
if (dtypedescr != NULL) { | ||
ret = PyArray_DescrConverter(dtypedescr, &newdescr); | ||
Py_DECREF(dtypedescr); | ||
if (ret == NPY_SUCCEED) { | ||
return newdescr; | ||
} | ||
if (dtypedescr == NULL) { | ||
return NULL; | ||
} | ||
|
||
ret = PyArray_DescrConverter(dtypedescr, &newdescr); | ||
Py_DECREF(dtypedescr); | ||
if (ret != NPY_SUCCEED) { | ||
PyErr_Clear(); | ||
return NULL; | ||
} | ||
return _arraydescr_fromctypes(obj); | ||
|
||
return newdescr; | ||
} | ||
|
||
/* | ||
|
@@ -1423,10 +1393,20 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at) | |
check_num = NPY_VOID; | ||
} | ||
else { | ||
*at = _arraydescr_fromobj(obj); | ||
*at = _arraydescr_from_dtype_attr(obj); | ||
if (*at) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error return here is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole function seems to use the style of not comparing against |
||
return NPY_SUCCEED; | ||
} | ||
|
||
/* | ||
* Note: this comes after _arraydescr_from_dtype_attr because the ctypes | ||
* type might override the dtype if numpy does not otherwise | ||
* support it. | ||
*/ | ||
if (npy_ctypes_check((PyTypeObject *)obj)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally prefer to check against value, especially as the error return is '0'. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather leave this as is, since it matches things like |
||
*at = _arraydescr_from_ctypes_type((PyTypeObject *)obj); | ||
return *at ? NPY_SUCCEED : NPY_FAIL; | ||
} | ||
} | ||
goto finish; | ||
} | ||
|
@@ -1596,13 +1576,23 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at) | |
goto fail; | ||
} | ||
else { | ||
*at = _arraydescr_fromobj(obj); | ||
*at = _arraydescr_from_dtype_attr(obj); | ||
if (*at) { | ||
return NPY_SUCCEED; | ||
} | ||
if (PyErr_Occurred()) { | ||
return NPY_FAIL; | ||
} | ||
|
||
/* | ||
* Note: this comes after _arraydescr_from_dtype_attr because the ctypes | ||
* type might override the dtype if numpy does not otherwise | ||
* support it. | ||
*/ | ||
if (npy_ctypes_check(Py_TYPE(obj))) { | ||
*at = _arraydescr_from_ctypes_type(Py_TYPE(obj)); | ||
return *at ? NPY_SUCCEED : NPY_FAIL; | ||
} | ||
goto fail; | ||
} | ||
if (PyErr_Occurred()) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are they ever otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a
_pack_
attribute that makes them otherwise: #10532