Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

int and set subclass __sizeof__ under-reports the instance dictionary pointer #101266

Closed
ionite34 opened this issue Jan 23, 2023 · 3 comments
Closed
Assignees
Labels
type-bug An unexpected behavior, bug, or error

Comments

@ionite34
Copy link
Contributor

ionite34 commented Jan 23, 2023

Bug report

Most built-in types, when subclassed, will have a __sizeof__ that is 8 (pointer size) larger than the parent object, due to having an instance dictionary.

class UserFloat(float): pass
assert UserFloat().__sizeof__() - (0.0).__sizeof__() == 8

class UserTuple(tuple): pass
assert UserTuple().__sizeof__() - ().__sizeof__() == 8

class UserList(list): pass
assert UserList().__sizeof__() - [].__sizeof__() == 8

class UserDict(dict): pass
assert UserDict().__sizeof__() - {}.__sizeof__() == 8

This is unexpectedly, not the case for int and set, which exactly match the original __sizeof__

class UserSet(set): pass
print(UserSet().__sizeof__(), set().__sizeof__())  # 200 200

class UserInt(int): pass
print(UserInt().__sizeof__(), (0).__sizeof__())  # 24 24

As a result this makes __dictoffset__ usages incorrect as well

from ctypes import py_object

class UserTuple(tuple): pass

x = UserTuple()
x.a = 5
print(x.__dict__)
>> {'a': 5}
py_object.from_address(id(x) + x.__sizeof__() + UserTuple.__dictoffset__)
>> py_object({'a': 5})
from ctypes import py_object

class UserInt(int): pass

x = UserInt()
x.a = 5
print(x.__dict__)
>> {'a': 5}
py_object.from_address(id(x) + x.__sizeof__() + UserInt.__dictoffset__)
>> py_object(<NULL>)

Your environment

  • CPython versions tested on: 3.11, 3.12.0a3

Linked PRs

@ionite34 ionite34 added the type-bug An unexpected behavior, bug, or error label Jan 23, 2023
@mdickinson
Copy link
Member

I'll leave the set part of this to someone more qualified. (@rhettinger?)

For int, I think this is an easy fix; I'll make a PR shortly.

Though oddly enough, I believe the current behaviour in main is correct (though for the wrong reasons), since UserInt.__basicsize__ now matches int.__basicsize__.

@mdickinson
Copy link
Member

mdickinson commented Jan 28, 2023

Actually, I'm not sure there's any bug here for set: the __dict__ pointer for a UserSet instance is stored in the preheader (the flags for UserSet include Py_TPFLAGS_MANAGED_DICT), and is accounted for there when computing the size with sys.getsizeof.

Python 3.12.0a4 (main, Jan 20 2023, 16:04:05) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class UserSet(set): pass
... 
>>> s = {1, 2, 3}
>>> us = UserSet(s)
>>> import sys
>>> sys.getsizeof(us) - sys.getsizeof(s)
16

mdickinson added a commit that referenced this issue Feb 5, 2023
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 -->
mdickinson added a commit that referenced this issue Feb 5, 2023
…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.)
@mdickinson
Copy link
Member

mdickinson commented Feb 5, 2023

The int side of this is fixed in main, with the fix backported to 3.11 (but not to 3.10, since the "correct" behaviour is a bit less clear there, and since we didn't backport the previous fix to 3.10).

I believe there's no actual issue on the set side of things (@ionite34 please comment if you disagree), so I'm going to close here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants