[Python-checkins] [3.11] gh-101266: Fix __sizeof__ for subclasses of int (GH-101394) (#101579)

mdickinson webhook-mailer at python.org
Sun Feb 5 07:19:01 EST 2023


https://github.com/python/cpython/commit/cf89c16486a4cc297413e17d32082ec4f389d725
commit: cf89c16486a4cc297413e17d32082ec4f389d725
branch: 3.11
author: Mark Dickinson <dickinsm at gmail.com>
committer: mdickinson <dickinsm at gmail.com>
date: 2023-02-05T12:18:56Z
summary:

[3.11] gh-101266: Fix __sizeof__ for subclasses of int (GH-101394) (#101579)

Fix the behaviour of the `__sizeof__` method (and hence the results returned
by `sys.getsizeof`) for subclasses of `int`. Previously, `int` subclasses gave
identical results to the `int` base class, ignoring the presence of the instance
dictionary.

(Manual backport of #101394 to the Python 3.11 branch.)

files:
A Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst
M Lib/test/test_long.py
M Objects/boolobject.c
M Objects/longobject.c

diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py
index 77b37ca1fa4a..03bed4c41f5a 100644
--- a/Lib/test/test_long.py
+++ b/Lib/test/test_long.py
@@ -1596,5 +1596,44 @@ def test_square(self):
                 self.assertEqual(n**2,
                     (1 << (2 * bitlen)) - (1 << (bitlen + 1)) + 1)
 
+    def test___sizeof__(self):
+        self.assertEqual(int.__itemsize__, sys.int_info.sizeof_digit)
+
+        # Pairs (test_value, number of allocated digits)
+        test_values = [
+            # We always allocate space for at least one digit, even for
+            # a value of zero; sys.getsizeof should reflect that.
+            (0, 1),
+            (1, 1),
+            (-1, 1),
+            (BASE-1, 1),
+            (1-BASE, 1),
+            (BASE, 2),
+            (-BASE, 2),
+            (BASE*BASE - 1, 2),
+            (BASE*BASE, 3),
+        ]
+
+        for value, ndigits in test_values:
+            with self.subTest(value):
+                self.assertEqual(
+                    value.__sizeof__(),
+                    int.__basicsize__ + int.__itemsize__ * ndigits
+                )
+
+        # Same test for a subclass of int.
+        class MyInt(int):
+            pass
+
+        self.assertEqual(MyInt.__itemsize__, sys.int_info.sizeof_digit)
+
+        for value, ndigits in test_values:
+            with self.subTest(value):
+                self.assertEqual(
+                    MyInt(value).__sizeof__(),
+                    MyInt.__basicsize__ + MyInt.__itemsize__ * ndigits
+                )
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst b/Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst
new file mode 100644
index 000000000000..51999bacb8de
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst	
@@ -0,0 +1 @@
+Fix :func:`sys.getsizeof` reporting for :class:`int` subclasses.
diff --git a/Objects/boolobject.c b/Objects/boolobject.c
index 8a20e368d4a4..01acf0f00b66 100644
--- a/Objects/boolobject.c
+++ b/Objects/boolobject.c
@@ -4,6 +4,8 @@
 #include "pycore_object.h"      // _Py_FatalRefcountError()
 #include "pycore_runtime.h"       // _Py_ID()
 
+#include <stddef.h>
+
 /* We define bool_repr to return "False" or "True" */
 
 static PyObject *
@@ -154,8 +156,8 @@ bool_dealloc(PyObject* Py_UNUSED(ignore))
 PyTypeObject PyBool_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     "bool",
-    sizeof(struct _longobject),
-    0,
+    offsetof(struct _longobject, ob_digit),     /* tp_basicsize */
+    sizeof(digit),                              /* tp_itemsize */
     bool_dealloc,                               /* tp_dealloc */
     0,                                          /* tp_vectorcall_offset */
     0,                                          /* tp_getattr */
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 84c05e8aabdf..39bd72ce5598 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -5664,13 +5664,10 @@ static Py_ssize_t
 int___sizeof___impl(PyObject *self)
 /*[clinic end generated code: output=3303f008eaa6a0a5 input=9b51620c76fc4507]*/
 {
-    Py_ssize_t res;
-
-    res = offsetof(PyLongObject, ob_digit)
-        /* using Py_MAX(..., 1) because we always allocate space for at least
-           one digit, even though the integer zero has a Py_SIZE of 0 */
-        + Py_MAX(Py_ABS(Py_SIZE(self)), 1)*sizeof(digit);
-    return res;
+    /* using Py_MAX(..., 1) because we always allocate space for at least
+       one digit, even though the integer zero has a Py_SIZE of 0 */
+    Py_ssize_t ndigits = Py_MAX(Py_ABS(Py_SIZE(self)), 1);
+    return Py_TYPE(self)->tp_basicsize + Py_TYPE(self)->tp_itemsize * ndigits;
 }
 
 /*[clinic input]



More information about the Python-checkins mailing list