Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5386540

Browse files
authored
ENH: Improve performance of numpy scalar __copy__ and __deepcopy__ (#29656)
The numpy scalars are immutable (expect for subclasses of np.void), so copy or deepcopy can return the same object. This improves performance, since the current implementation converts to array, copies and converts back to scalar.
1 parent 420967e commit 5386540

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

numpy/_core/src/multiarray/scalartypes.c.src

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2093,12 +2093,11 @@ gentype_wraparray(PyObject *NPY_UNUSED(scalar), PyObject *args)
20932093

20942094
/*
20952095
* These gentype_* functions do not take keyword arguments.
2096-
* The proper flag is METH_VARARGS.
2096+
* The proper flag is METH_VARARGS or METH_NOARGS.
20972097
*/
20982098
/**begin repeat
20992099
*
2100-
* #name = tolist, item, __deepcopy__, __copy__,
2101-
* swapaxes, conj, conjugate, nonzero,
2100+
* #name = tolist, item, swapaxes, conj, conjugate, nonzero,
21022101
* fill, transpose#
21032102
*/
21042103
static PyObject *
@@ -2108,6 +2107,34 @@ gentype_@name@(PyObject *self, PyObject *args)
21082107
}
21092108
/**end repeat**/
21102109

2110+
static PyObject *
2111+
gentype___copy__(PyObject *self)
2112+
{
2113+
// scalars are immutable, so we can return a new reference
2114+
// the only expections are scalars with void dtype
2115+
if (PyObject_IsInstance(self, (PyObject *)&PyVoidArrType_Type)) {
2116+
// path via array
2117+
return gentype_generic_method(self, NULL, NULL, "__copy__");
2118+
}
2119+
return Py_NewRef(self);
2120+
}
2121+
2122+
static PyObject *
2123+
gentype___deepcopy__(PyObject *self, PyObject *args)
2124+
{
2125+
// note: maybe the signature needs to be updated as __deepcopy__ can accept the keyword memo
2126+
2127+
// scalars are immutable, so we can return a new reference
2128+
// the only expections are scalars with void dtype
2129+
// if the number of arguments is not 1, we let gentype_generic_method do the
2130+
// error handling
2131+
if (PyObject_IsInstance(self, (PyObject *)&PyVoidArrType_Type) || (PyTuple_Size(args)!=1)) {
2132+
// path via array
2133+
return gentype_generic_method(self, args, NULL, "__deepcopy__");
2134+
}
2135+
return Py_NewRef(self);
2136+
}
2137+
21112138
static PyObject *
21122139
gentype_byteswap(PyObject *self, PyObject *args, PyObject *kwds)
21132140
{
@@ -2652,7 +2679,7 @@ static PyMethodDef gentype_methods[] = {
26522679
/* for the copy module */
26532680
{"__copy__",
26542681
(PyCFunction)gentype___copy__,
2655-
METH_VARARGS, NULL},
2682+
METH_NOARGS, NULL},
26562683
{"__deepcopy__",
26572684
(PyCFunction)gentype___deepcopy__,
26582685
METH_VARARGS, NULL},

numpy/_core/tests/test_multiarray.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,6 +2476,20 @@ def test__deepcopy__(self, dtype):
24762476
with pytest.raises(AssertionError):
24772477
assert_array_equal(a, b)
24782478

2479+
def test__deepcopy___void_scalar(self):
2480+
# see comments in gh-29643
2481+
value = np.void('Rex', dtype=[('name', 'U10')])
2482+
value_deepcopy = value.__deepcopy__(None)
2483+
value[0] = None
2484+
assert value_deepcopy[0] == 'Rex'
2485+
2486+
@pytest.mark.parametrize("sctype", [np.int64, np.float32, np.float64])
2487+
def test__deepcopy__scalar(self, sctype):
2488+
# test optimization from gh-29656
2489+
value = sctype(1.1)
2490+
value_deepcopy = value.__deepcopy__(None)
2491+
assert value is value_deepcopy
2492+
24792493
def test__deepcopy__catches_failure(self):
24802494
class MyObj:
24812495
def __deepcopy__(self, *args, **kwargs):

0 commit comments

Comments
 (0)