Skip to content

Commit b22a613

Browse files
authored
Merge pull request #31211 from charris/backport-31177
DEP: Undo deprecation for np.dtype() signature used by old pickles (#31177)
2 parents fbe39b8 + 7c2d563 commit b22a613

4 files changed

Lines changed: 38 additions & 31 deletions

File tree

numpy/_core/src/multiarray/descriptor.c

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2544,6 +2544,7 @@ arraydescr_new(PyTypeObject *subtype,
25442544

25452545
PyObject *odescr;
25462546
PyObject *oalign = NULL;
2547+
PyObject *ocopy = NULL;
25472548
PyObject *metadata = NULL;
25482549
PyArray_Descr *conv;
25492550
npy_bool align = NPY_FALSE;
@@ -2552,21 +2553,36 @@ arraydescr_new(PyTypeObject *subtype,
25522553

25532554
static char *kwlist[] = {"dtype", "align", "copy", "metadata", NULL};
25542555

2555-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OO&O!:dtype", kwlist,
2556+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOO!:dtype", kwlist,
25562557
&odescr,
25572558
&oalign,
2558-
PyArray_BoolConverter, &copy,
2559+
&ocopy,
25592560
&PyDict_Type, &metadata)) {
25602561
return NULL;
25612562
}
25622563

2564+
if (ocopy != NULL && !PyArray_BoolConverter(ocopy, &copy)) {
2565+
return NULL;
2566+
}
25632567
if (oalign != NULL) {
25642568
/*
25652569
* In the future, reject non Python (or NumPy) boolean, including integers to avoid any
25662570
* possibility of thinking that an integer alignment makes sense here.
2571+
* We omit the case of `oalign == 0` and `ocopy == 1` if there are exact ints.
2572+
* This can fail, in which case res is -1 and we enter the deprecation path.
25672573
*/
2568-
if (!PyBool_Check(oalign) && !PyArray_IsScalar(oalign, Bool)) {
2574+
int res = 0;
2575+
int overflow;
2576+
if (!PyBool_Check(oalign) && !PyArray_IsScalar(oalign, Bool) && !(
2577+
// Some old pickles use 0, 1 exactly, assume no user passes it
2578+
// (It may also be possible to use `copyreg` instead.)
2579+
PyLong_CheckExact(oalign) && (res = PyLong_IsZero(oalign)) == 1 &&
2580+
ocopy != NULL && PyLong_CheckExact(ocopy) &&
2581+
(res = PyLong_AsLongAndOverflow(ocopy, &overflow)) == 1)) {
25692582
/* Deprecated 2025-07-01: NumPy 2.4 */
2583+
if (res == -1 && PyErr_Occurred()) {
2584+
return NULL; // Should actually be impossible (as inputs are `long`)
2585+
}
25702586
if (PyErr_WarnFormat(npy_static_pydata.VisibleDeprecationWarning, 1,
25712587
"dtype(): align should be passed as Python or NumPy boolean but got `align=%.100R`. "
25722588
"Did you mean to pass a tuple to create a subarray type? (Deprecated NumPy 2.4)",

numpy/_core/tests/test_datetime.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -874,20 +874,18 @@ def test_pickle(self):
874874
delta)
875875

876876
# Check that loading pickles from 1.6 works
877-
with pytest.warns(np.exceptions.VisibleDeprecationWarning,
878-
match=r".*align should be passed"):
879-
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
880-
b"(I4\nS'<'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'D'\np6\n"\
881-
b"I7\nI1\nI1\ntp7\ntp8\ntp9\nb."
882-
assert_equal(pickle.loads(pkl), np.dtype('<M8[7D]'))
883-
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
884-
b"(I4\nS'<'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'W'\np6\n"\
885-
b"I1\nI1\nI1\ntp7\ntp8\ntp9\nb."
886-
assert_equal(pickle.loads(pkl), np.dtype('<M8[W]'))
887-
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
888-
b"(I4\nS'>'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'us'\np6\n"\
889-
b"I1\nI1\nI1\ntp7\ntp8\ntp9\nb."
890-
assert_equal(pickle.loads(pkl), np.dtype('>M8[us]'))
877+
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
878+
b"(I4\nS'<'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'D'\np6\n"\
879+
b"I7\nI1\nI1\ntp7\ntp8\ntp9\nb."
880+
assert_equal(pickle.loads(pkl), np.dtype('<M8[7D]'))
881+
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
882+
b"(I4\nS'<'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'W'\np6\n"\
883+
b"I1\nI1\nI1\ntp7\ntp8\ntp9\nb."
884+
assert_equal(pickle.loads(pkl), np.dtype('<M8[W]'))
885+
pkl = b"cnumpy\ndtype\np0\n(S'M8'\np1\nI0\nI1\ntp2\nRp3\n"\
886+
b"(I4\nS'>'\np4\nNNNI-1\nI-1\nI0\n((dp5\n(S'us'\np6\n"\
887+
b"I1\nI1\nI1\ntp7\ntp8\ntp9\nb."
888+
assert_equal(pickle.loads(pkl), np.dtype('>M8[us]'))
891889

892890
def test_gh_29555(self):
893891
# check that dtype metadata round-trips when none

numpy/_core/tests/test_deprecations.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,11 +386,18 @@ def test_deprecated(self):
386386
# alignment, or pass them accidentally as a subarray shape (meaning to pass
387387
# a tuple).
388388
self.assert_deprecated(lambda: np.dtype("f8", align=3))
389+
self.assert_deprecated(lambda: np.dtype("f8", align=0, copy=10**100))
390+
self.assert_deprecated(lambda: np.dtype("f8", align=10**100, copy=0))
391+
# Subclasses of ints don't hit the below pickle code path:
392+
self.assert_deprecated(
393+
lambda: np.dtype("f8", align=np.long(0), copy=np.long(1)))
389394

390395
@pytest.mark.parametrize("align", [True, False, np.True_, np.False_])
391396
def test_not_deprecated(self, align):
392397
# if the user passes a bool, it is accepted.
393398
self.assert_not_deprecated(lambda: np.dtype("f8", align=align))
399+
# The following specific case is used by old pickles:
400+
self.assert_not_deprecated(lambda: np.dtype("f8", align=0, copy=1))
394401

395402

396403
class TestFlatiterIndexing0dBoolIndex(_DeprecationTestCase):

numpy/_core/tests/test_multiarray.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4681,57 +4681,43 @@ def _loads(self, obj):
46814681

46824682
# version 0 pickles, using protocol=2 to pickle
46834683
# version 0 doesn't have a version field
4684-
@pytest.mark.filterwarnings(
4685-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
46864684
def test_version0_int8(self):
46874685
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x04\x85cnumpy\ndtype\nq\x04U\x02i1K\x00K\x01\x87Rq\x05(U\x01|NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89U\x04\x01\x02\x03\x04tb."
46884686
a = np.array([1, 2, 3, 4], dtype=np.int8)
46894687
p = self._loads(s)
46904688
assert_equal(a, p)
46914689

4692-
@pytest.mark.filterwarnings(
4693-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
46944690
def test_version0_float32(self):
46954691
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x04\x85cnumpy\ndtype\nq\x04U\x02f4K\x00K\x01\x87Rq\x05(U\x01<NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89U\x10\x00\x00\x80?\x00\x00\x00@\x00\x00@@\x00\x00\x80@tb."
46964692
a = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
46974693
p = self._loads(s)
46984694
assert_equal(a, p)
46994695

4700-
@pytest.mark.filterwarnings(
4701-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
47024696
def test_version0_object(self):
47034697
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x02\x85cnumpy\ndtype\nq\x04U\x02O8K\x00K\x01\x87Rq\x05(U\x01|NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89]q\x06(}q\x07U\x01aK\x01s}q\x08U\x01bK\x02setb."
47044698
a = np.array([{'a': 1}, {'b': 2}])
47054699
p = self._loads(s)
47064700
assert_equal(a, p)
47074701

47084702
# version 1 pickles, using protocol=2 to pickle
4709-
@pytest.mark.filterwarnings(
4710-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
47114703
def test_version1_int8(self):
47124704
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x01K\x04\x85cnumpy\ndtype\nq\x04U\x02i1K\x00K\x01\x87Rq\x05(K\x01U\x01|NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89U\x04\x01\x02\x03\x04tb."
47134705
a = np.array([1, 2, 3, 4], dtype=np.int8)
47144706
p = self._loads(s)
47154707
assert_equal(a, p)
47164708

4717-
@pytest.mark.filterwarnings(
4718-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
47194709
def test_version1_float32(self):
47204710
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x01K\x04\x85cnumpy\ndtype\nq\x04U\x02f4K\x00K\x01\x87Rq\x05(K\x01U\x01<NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89U\x10\x00\x00\x80?\x00\x00\x00@\x00\x00@@\x00\x00\x80@tb."
47214711
a = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
47224712
p = self._loads(s)
47234713
assert_equal(a, p)
47244714

4725-
@pytest.mark.filterwarnings(
4726-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
47274715
def test_version1_object(self):
47284716
s = b"\x80\x02cnumpy.core._internal\n_reconstruct\nq\x01cnumpy\nndarray\nq\x02K\x00\x85U\x01b\x87Rq\x03(K\x01K\x02\x85cnumpy\ndtype\nq\x04U\x02O8K\x00K\x01\x87Rq\x05(K\x01U\x01|NNJ\xff\xff\xff\xffJ\xff\xff\xff\xfftb\x89]q\x06(}q\x07U\x01aK\x01s}q\x08U\x01bK\x02setb."
47294717
a = np.array([{'a': 1}, {'b': 2}])
47304718
p = self._loads(s)
47314719
assert_equal(a, p)
47324720

4733-
@pytest.mark.filterwarnings(
4734-
"ignore:.*align should be passed:numpy.exceptions.VisibleDeprecationWarning")
47354721
def test_subarray_int_shape(self):
47364722
s = b"cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I1\ntp6\ncnumpy\ndtype\np7\n(S'V6'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nN(S'a'\np12\ng3\ntp13\n(dp14\ng12\n(g7\n(S'V4'\np15\nI0\nI1\ntp16\nRp17\n(I3\nS'|'\np18\n(g7\n(S'i1'\np19\nI0\nI1\ntp20\nRp21\n(I3\nS'|'\np22\nNNNI-1\nI-1\nI0\ntp23\nb(I2\nI2\ntp24\ntp25\nNNI4\nI1\nI0\ntp26\nbI0\ntp27\nsg3\n(g7\n(S'V2'\np28\nI0\nI1\ntp29\nRp30\n(I3\nS'|'\np31\n(g21\nI2\ntp32\nNNI2\nI1\nI0\ntp33\nbI4\ntp34\nsI6\nI1\nI0\ntp35\nbI00\nS'\\x01\\x01\\x01\\x01\\x01\\x02'\np36\ntp37\nb."
47374723
a = np.array([(1, (1, 2))], dtype=[('a', 'i1', (2, 2)), ('b', 'i1', 2)])

0 commit comments

Comments
 (0)