summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark Dickinson <dickinsm@gmail.com>2023-02-05 10:02:53 (GMT)
committerGitHub <noreply@github.com>2023-02-05 10:02:53 (GMT)
commit39017e04b55d4c110787551dc9a0cb753f27d700 (patch)
tree43c39ae5fa296b5bf195614330b0441d6c96db7a
parent9b60ee976a6b66fe96c2d39051612999c26561e5 (diff)
downloadcpython-39017e04b55d4c110787551dc9a0cb753f27d700.zip
cpython-39017e04b55d4c110787551dc9a0cb753f27d700.tar.gz
cpython-39017e04b55d4c110787551dc9a0cb753f27d700.tar.bz2
gh-101266: Fix __sizeof__ for subclasses of int (#101394)
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 -->
-rw-r--r--Lib/test/test_long.py39
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-01-28-13-11-52.gh-issue-101266.AxV3OF.rst1
-rw-r--r--Objects/boolobject.c6
-rw-r--r--Objects/longobject.c11
4 files changed, 48 insertions, 9 deletions
diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py
index 569ab15..d299c34 100644
--- a/Lib/test/test_long.py
+++ b/Lib/test/test_long.py
@@ -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
index 0000000..51999ba
--- /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 55b4a40..a035f46 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 *
@@ -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 */
diff --git a/Objects/longobject.c b/Objects/longobject.c
index 65bf156..8293f13 100644
--- a/Objects/longobject.c
+++ b/Objects/longobject.c
@@ -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]