Skip to content

Commit cf89c16

Browse files
authored
[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.)
1 parent 7cbcfbe commit cf89c16

File tree

4 files changed

+48
-9
lines changed

4 files changed

+48
-9
lines changed

Lib/test/test_long.py

+39
Original file line numberDiff line numberDiff line change
@@ -1596,5 +1596,44 @@ def test_square(self):
15961596
self.assertEqual(n**2,
15971597
(1 << (2 * bitlen)) - (1 << (bitlen + 1)) + 1)
15981598

1599+
def test___sizeof__(self):
1600+
self.assertEqual(int.__itemsize__, sys.int_info.sizeof_digit)
1601+
1602+
# Pairs (test_value, number of allocated digits)
1603+
test_values = [
1604+
# We always allocate space for at least one digit, even for
1605+
# a value of zero; sys.getsizeof should reflect that.
1606+
(0, 1),
1607+
(1, 1),
1608+
(-1, 1),
1609+
(BASE-1, 1),
1610+
(1-BASE, 1),
1611+
(BASE, 2),
1612+
(-BASE, 2),
1613+
(BASE*BASE - 1, 2),
1614+
(BASE*BASE, 3),
1615+
]
1616+
1617+
for value, ndigits in test_values:
1618+
with self.subTest(value):
1619+
self.assertEqual(
1620+
value.__sizeof__(),
1621+
int.__basicsize__ + int.__itemsize__ * ndigits
1622+
)
1623+
1624+
# Same test for a subclass of int.
1625+
class MyInt(int):
1626+
pass
1627+
1628+
self.assertEqual(MyInt.__itemsize__, sys.int_info.sizeof_digit)
1629+
1630+
for value, ndigits in test_values:
1631+
with self.subTest(value):
1632+
self.assertEqual(
1633+
MyInt(value).__sizeof__(),
1634+
MyInt.__basicsize__ + MyInt.__itemsize__ * ndigits
1635+
)
1636+
1637+
15991638
if __name__ == "__main__":
16001639
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :func:`sys.getsizeof` reporting for :class:`int` subclasses.

Objects/boolobject.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "pycore_object.h" // _Py_FatalRefcountError()
55
#include "pycore_runtime.h" // _Py_ID()
66

7+
#include <stddef.h>
8+
79
/* We define bool_repr to return "False" or "True" */
810

911
static PyObject *
@@ -154,8 +156,8 @@ bool_dealloc(PyObject* Py_UNUSED(ignore))
154156
PyTypeObject PyBool_Type = {
155157
PyVarObject_HEAD_INIT(&PyType_Type, 0)
156158
"bool",
157-
sizeof(struct _longobject),
158-
0,
159+
offsetof(struct _longobject, ob_digit), /* tp_basicsize */
160+
sizeof(digit), /* tp_itemsize */
159161
bool_dealloc, /* tp_dealloc */
160162
0, /* tp_vectorcall_offset */
161163
0, /* tp_getattr */

Objects/longobject.c

+4-7
Original file line numberDiff line numberDiff line change
@@ -5664,13 +5664,10 @@ static Py_ssize_t
56645664
int___sizeof___impl(PyObject *self)
56655665
/*[clinic end generated code: output=3303f008eaa6a0a5 input=9b51620c76fc4507]*/
56665666
{
5667-
Py_ssize_t res;
5668-
5669-
res = offsetof(PyLongObject, ob_digit)
5670-
/* using Py_MAX(..., 1) because we always allocate space for at least
5671-
one digit, even though the integer zero has a Py_SIZE of 0 */
5672-
+ Py_MAX(Py_ABS(Py_SIZE(self)), 1)*sizeof(digit);
5673-
return res;
5667+
/* using Py_MAX(..., 1) because we always allocate space for at least
5668+
one digit, even though the integer zero has a Py_SIZE of 0 */
5669+
Py_ssize_t ndigits = Py_MAX(Py_ABS(Py_SIZE(self)), 1);
5670+
return Py_TYPE(self)->tp_basicsize + Py_TYPE(self)->tp_itemsize * ndigits;
56745671
}
56755672

56765673
/*[clinic input]

0 commit comments

Comments
 (0)