]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-101266: Fix __sizeof__ for subclasses of int (#101394)
authorMark Dickinson <dickinsm@gmail.com>
Sun, 5 Feb 2023 10:02:53 +0000 (10:02 +0000)
committerGitHub <noreply@github.com>
Sun, 5 Feb 2023 10:02:53 +0000 (10:02 +0000)
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.

<!-- gh-issue-number: gh-101266 -->
* Issue: gh-101266
<!-- /gh-issue-number -->

Lib/test/test_long.py
Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst [new file with mode: 0644]
Objects/boolobject.c
Objects/longobject.c

index 569ab15820e3023e13ec0e5a5501fc4c58bd1b20..d299c34cec076d0468461e7330b3508e736c1470 100644 (file)
@@ -1601,5 +1601,44 @@ class LongTest(unittest.TestCase):
                 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 (file)
index 0000000..51999ba
--- /dev/null
@@ -0,0 +1 @@
+Fix :func:`sys.getsizeof` reporting for :class:`int` subclasses.
index 55b4a4077ab7831d34c19813a47e7e3ee9e6b0d6..a035f463323823afbdc71849140433e0834643b9 100644 (file)
@@ -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 *
@@ -153,8 +155,8 @@ bool_dealloc(PyObject* Py_UNUSED(ignore))
 PyTypeObject PyBool_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     "bool",
-    sizeof(struct _longobject),
-    0,
+    offsetof(struct _longobject, long_value.ob_digit),  /* tp_basicsize */
+    sizeof(digit),                              /* tp_itemsize */
     bool_dealloc,                               /* tp_dealloc */
     0,                                          /* tp_vectorcall_offset */
     0,                                          /* tp_getattr */
index 65bf15648b07fb336a6c47935407950a1f46aaad..8293f133bed213551971b2b37e30a57fadb32207 100644 (file)
@@ -5882,13 +5882,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, long_value.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]