summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/arrayobject.c3
-rw-r--r--numpy/core/src/multiarray/arraytypes.c.src2
-rw-r--r--numpy/core/src/multiarray/ctors.c23
-rw-r--r--numpy/core/src/umath/loops.c.src16
-rw-r--r--numpy/core/tests/test_casting_unittests.py4
-rw-r--r--numpy/core/tests/test_multiarray.py15
6 files changed, 56 insertions, 7 deletions
diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c
index d18fe1b10..b1302738d 100644
--- a/numpy/core/src/multiarray/arrayobject.c
+++ b/numpy/core/src/multiarray/arrayobject.c
@@ -1261,7 +1261,8 @@ array_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
descr = NULL;
goto fail;
}
- if (PyDataType_FLAGCHK(descr, NPY_ITEM_HASOBJECT)) {
+ /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */
+ if (PyDataType_REFCHK(PyArray_DESCR(ret))) {
/* place Py_None in object positions */
PyArray_FillObjectArray(ret, Py_None);
if (PyErr_Occurred()) {
diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src
index a9f8dfdd2..2539fdb57 100644
--- a/numpy/core/src/multiarray/arraytypes.c.src
+++ b/numpy/core/src/multiarray/arraytypes.c.src
@@ -716,6 +716,7 @@ OBJECT_getitem(void *ip, void *NPY_UNUSED(ap))
PyObject *obj;
memcpy(&obj, ip, sizeof(obj));
if (obj == NULL) {
+ /* We support NULL, but still try to guarantee this never happens! */
Py_RETURN_NONE;
}
else {
@@ -733,6 +734,7 @@ OBJECT_setitem(PyObject *op, void *ov, void *NPY_UNUSED(ap))
memcpy(&obj, ov, sizeof(obj));
Py_INCREF(op);
+ /* A newly created array/buffer may only be NULLed, so XDECREF */
Py_XDECREF(obj);
memcpy(ov, &op, sizeof(op));
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index c3d66dd6b..ebd990724 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -1068,6 +1068,18 @@ PyArray_NewLikeArrayWithShape(PyArrayObject *prototype, NPY_ORDER order,
0,
subok ? (PyObject *)prototype : NULL);
}
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */
+ if (PyDataType_REFCHK(PyArray_DESCR((PyArrayObject *)ret))) {
+ PyArray_FillObjectArray((PyArrayObject *)ret, Py_None);
+ if (PyErr_Occurred()) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+ }
return ret;
}
@@ -2979,22 +2991,23 @@ PyArray_Empty(int nd, npy_intp const *dims, PyArray_Descr *type, int is_f_order)
* PyArray_NewFromDescr steals a ref,
* but we need to look at type later.
* */
- Py_INCREF(type);
-
ret = (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type,
type, nd, dims,
NULL, NULL,
is_f_order, NULL);
- if (ret != NULL && PyDataType_REFCHK(type)) {
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ /* Logic shared by `empty`, `empty_like`, and `ndarray.__new__` */
+ if (PyDataType_REFCHK(PyArray_DESCR(ret))) {
PyArray_FillObjectArray(ret, Py_None);
if (PyErr_Occurred()) {
Py_DECREF(ret);
- Py_DECREF(type);
return NULL;
}
}
- Py_DECREF(type);
return (PyObject *)ret;
}
diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src
index 9ae686399..e5104db81 100644
--- a/numpy/core/src/umath/loops.c.src
+++ b/numpy/core/src/umath/loops.c.src
@@ -214,6 +214,8 @@ PyUFunc_O_O(char **args, npy_intp const *dimensions, npy_intp const *steps, void
UNARY_LOOP {
PyObject *in1 = *(PyObject **)ip1;
PyObject **out = (PyObject **)op1;
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in1 != NULL);
PyObject *ret = f(in1 ? in1 : Py_None);
if (ret == NULL) {
return;
@@ -231,6 +233,8 @@ PyUFunc_O_O_method(char **args, npy_intp const *dimensions, npy_intp const *step
UNARY_LOOP {
PyObject *in1 = *(PyObject **)ip1;
PyObject **out = (PyObject **)op1;
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in1 != NULL);
PyObject *ret, *func;
func = PyObject_GetAttrString(in1 ? in1 : Py_None, meth);
if (func != NULL && !PyCallable_Check(func)) {
@@ -268,6 +272,9 @@ PyUFunc_OO_O(char **args, npy_intp const *dimensions, npy_intp const *steps, voi
PyObject *in1 = *(PyObject **)ip1;
PyObject *in2 = *(PyObject **)ip2;
PyObject **out = (PyObject **)op1;
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in1 != NULL);
+ assert(in2 != NULL);
PyObject *ret = f(in1 ? in1 : Py_None, in2 ? in2 : Py_None);
if (ret == NULL) {
return;
@@ -286,6 +293,10 @@ PyUFunc_OOO_O(char **args, npy_intp const *dimensions, npy_intp const *steps, vo
PyObject *in2 = *(PyObject **)ip2;
PyObject *in3 = *(PyObject **)ip3;
PyObject **out = (PyObject **)op1;
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in1 != NULL);
+ assert(in2 != NULL);
+ assert(in3 != NULL);
PyObject *ret = f(
in1 ? in1 : Py_None,
in2 ? in2 : Py_None,
@@ -308,6 +319,9 @@ PyUFunc_OO_O_method(char **args, npy_intp const *dimensions, npy_intp const *ste
PyObject *in1 = *(PyObject **)ip1;
PyObject *in2 = *(PyObject **)ip2;
PyObject **out = (PyObject **)op1;
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in1 != NULL);
+ assert(in2 != NULL);
PyObject *ret = PyObject_CallMethod(in1 ? in1 : Py_None,
meth, "(O)", in2);
if (ret == NULL) {
@@ -349,6 +363,8 @@ PyUFunc_On_Om(char **args, npy_intp const *dimensions, npy_intp const *steps, vo
}
for(j = 0; j < nin; j++) {
in = *((PyObject **)ptrs[j]);
+ /* We allow NULL, but try to guarantee non-NULL to downstream */
+ assert(in != NULL);
if (in == NULL) {
in = Py_None;
}
diff --git a/numpy/core/tests/test_casting_unittests.py b/numpy/core/tests/test_casting_unittests.py
index 5c5ff55b4..16ecb1943 100644
--- a/numpy/core/tests/test_casting_unittests.py
+++ b/numpy/core/tests/test_casting_unittests.py
@@ -10,6 +10,7 @@ import pytest
import textwrap
import enum
import random
+import ctypes
import numpy as np
from numpy.lib.stride_tricks import as_strided
@@ -786,7 +787,8 @@ class TestCasting:
# None to <other> casts may succeed or fail, but a NULL'ed array must
# behave the same as one filled with None's.
arr_normal = np.array([None] * 5)
- arr_NULLs = np.empty_like([None] * 5)
+ arr_NULLs = np.empty_like(arr_normal)
+ ctypes.memset(arr_NULLs.ctypes.data, 0, arr_NULLs.nbytes)
# If the check fails (maybe it should) the test would lose its purpose:
assert arr_NULLs.tobytes() == b"\x00" * arr_NULLs.nbytes
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 84fdf545f..ef2fc7d2e 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -1172,6 +1172,21 @@ class TestCreation:
a = np.array([1, Decimal(1)])
a = np.array([[1], [Decimal(1)]])
+ @pytest.mark.parametrize("dtype", [object, "O,O", "O,(3)O", "(2,3)O"])
+ @pytest.mark.parametrize("function", [
+ np.ndarray, np.empty,
+ lambda shape, dtype: np.empty_like(np.empty(shape, dtype=dtype))])
+ def test_object_initialized_to_None(self, function, dtype):
+ # NumPy has support for object fields to be NULL (meaning None)
+ # but generally, we should always fill with the proper None, and
+ # downstream may rely on that. (For fully initialized arrays!)
+ arr = function(3, dtype=dtype)
+ # We expect a fill value of None, which is not NULL:
+ expected = np.array(None).tobytes()
+ expected = expected * (arr.nbytes // len(expected))
+ assert arr.tobytes() == expected
+
+
class TestStructured:
def test_subarray_field_access(self):
a = np.zeros((3, 5), dtype=[('a', ('i4', (2, 2)))])