sys.getsizeof()¶
The sys.getsizeof() function returns the size of an object in bytes. It's essential for understanding Python's memory usage and optimizing memory-critical applications.
import sys
Basic Usage¶
import sys
# Integers
print(sys.getsizeof(0)) # 24 bytes (small int)
print(sys.getsizeof(1)) # 28 bytes
print(sys.getsizeof(2**30)) # 32 bytes (larger int)
print(sys.getsizeof(2**100)) # 44 bytes (arbitrary precision)
# Strings
print(sys.getsizeof("")) # 49 bytes (empty string overhead)
print(sys.getsizeof("a")) # 50 bytes
print(sys.getsizeof("hello")) # 54 bytes
# Collections
print(sys.getsizeof([])) # 56 bytes (empty list)
print(sys.getsizeof({})) # 64 bytes (empty dict)
print(sys.getsizeof(set())) # 216 bytes (empty set)
Important Limitation: Shallow Size Only¶
getsizeof() returns the shallow size—it doesn't include the size of objects that the container references:
import sys
# List with integers
lst = [1, 2, 3, 4, 5]
print(sys.getsizeof(lst)) # ~96 bytes (just the list structure)
# Does NOT include the size of integers 1, 2, 3, 4, 5!
# List with strings
strings = ["hello", "world"]
print(sys.getsizeof(strings)) # ~72 bytes (just the list)
# Does NOT include the size of "hello" and "world"!
Visual Explanation¶
sys.getsizeof(lst) measures this:
┌─────────────────────────────────┐
│ List object (header + pointers) │ ← Measured
├─────────────────────────────────┤
│ ptr → 1 │ ← NOT measured
│ ptr → 2 │ ← NOT measured
│ ptr → 3 │ ← NOT measured
└─────────────────────────────────┘
Calculating Deep Size¶
To get the total memory including referenced objects, use recursion:
Simple Deep Size Function¶
import sys
def deep_getsizeof(obj, seen=None):
"""Recursively calculate total size of object and its contents."""
if seen is None:
seen = set()
obj_id = id(obj)
if obj_id in seen:
return 0
seen.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, dict):
size += sum(deep_getsizeof(k, seen) + deep_getsizeof(v, seen)
for k, v in obj.items())
elif isinstance(obj, (list, tuple, set, frozenset)):
size += sum(deep_getsizeof(item, seen) for item in obj)
elif hasattr(obj, '__dict__'):
size += deep_getsizeof(obj.__dict__, seen)
elif hasattr(obj, '__slots__'):
size += sum(deep_getsizeof(getattr(obj, slot), seen)
for slot in obj.__slots__ if hasattr(obj, slot))
return size
# Usage
data = {'name': 'Alice', 'scores': [95, 87, 92]}
print(sys.getsizeof(data)) # ~232 bytes (shallow)
print(deep_getsizeof(data)) # ~450 bytes (deep)
Using pympler Library¶
For production use, consider the pympler library:
from pympler import asizeof
data = {'name': 'Alice', 'scores': [95, 87, 92]}
print(asizeof.asizeof(data)) # Total deep size
Size of Built-in Types¶
Integers¶
Python integers have variable size based on magnitude:
import sys
# Small integers (CPython caches -5 to 256)
print(sys.getsizeof(0)) # 24 bytes
print(sys.getsizeof(1)) # 28 bytes
print(sys.getsizeof(256)) # 28 bytes
# Larger integers grow
print(sys.getsizeof(2**30)) # 32 bytes
print(sys.getsizeof(2**60)) # 36 bytes
print(sys.getsizeof(2**100)) # 44 bytes
print(sys.getsizeof(2**1000)) # 160 bytes
Floats¶
Floats have fixed size (64-bit IEEE 754):
print(sys.getsizeof(0.0)) # 24 bytes
print(sys.getsizeof(3.14159)) # 24 bytes
print(sys.getsizeof(1e308)) # 24 bytes (always same)
Strings¶
String size depends on content and encoding:
# ASCII strings (1 byte per char + overhead)
print(sys.getsizeof("")) # 49 bytes (overhead)
print(sys.getsizeof("a")) # 50 bytes
print(sys.getsizeof("hello")) # 54 bytes
# Unicode strings may use more bytes per char
print(sys.getsizeof("é")) # 74 bytes (Latin-1)
print(sys.getsizeof("中")) # 76 bytes (UCS-2)
print(sys.getsizeof("🐍")) # 80 bytes (UCS-4)
Lists¶
Lists have base overhead plus pointer storage:
# Empty list
print(sys.getsizeof([])) # 56 bytes
# Lists grow in chunks (over-allocation)
for n in range(10):
lst = list(range(n))
print(f"{n} items: {sys.getsizeof(lst)} bytes")
# Output shows growth pattern:
# 0 items: 56 bytes
# 1 items: 64 bytes
# 2 items: 72 bytes
# 3 items: 80 bytes
# 4 items: 88 bytes
# 5 items: 96 bytes (may jump)
# ...
Dictionaries¶
print(sys.getsizeof({})) # 64 bytes
print(sys.getsizeof({'a': 1})) # 184 bytes
print(sys.getsizeof({'a': 1, 'b': 2})) # 184 bytes (same bucket)
print(sys.getsizeof({i: i for i in range(10)})) # 352 bytes
Sets¶
print(sys.getsizeof(set())) # 216 bytes
print(sys.getsizeof({1})) # 216 bytes
print(sys.getsizeof({1, 2, 3})) # 216 bytes
print(sys.getsizeof(set(range(10)))) # 728 bytes
Tuples¶
Tuples are more memory-efficient than lists:
print(sys.getsizeof(())) # 40 bytes
print(sys.getsizeof((1,))) # 48 bytes
print(sys.getsizeof((1, 2, 3))) # 64 bytes
# Compare with list
print(sys.getsizeof([1, 2, 3])) # 80+ bytes
Object Size Comparison¶
import sys
def show_size(name, obj):
print(f"{name:20} {sys.getsizeof(obj):>8} bytes")
show_size("None", None)
show_size("True", True)
show_size("int 0", 0)
show_size("int 1", 1)
show_size("float", 3.14)
show_size("str ''", "")
show_size("str 'hello'", "hello")
show_size("bytes b''", b"")
show_size("list []", [])
show_size("list [1,2,3]", [1,2,3])
show_size("tuple ()", ())
show_size("tuple (1,2,3)", (1,2,3))
show_size("dict {}", {})
show_size("set()", set())
show_size("frozenset()", frozenset())
Output (approximate, varies by Python version):
None 16 bytes
True 28 bytes
int 0 24 bytes
int 1 28 bytes
float 24 bytes
str '' 49 bytes
str 'hello' 54 bytes
bytes b'' 33 bytes
list [] 56 bytes
list [1,2,3] 80 bytes
tuple () 40 bytes
tuple (1,2,3) 64 bytes
dict {} 64 bytes
set() 216 bytes
frozenset() 216 bytes
Custom Objects¶
With __dict__¶
class Regular:
def __init__(self, x, y):
self.x = x
self.y = y
obj = Regular(1, 2)
print(sys.getsizeof(obj)) # ~48 bytes (object)
print(sys.getsizeof(obj.__dict__)) # ~104 bytes (attribute dict)
# Total: ~152 bytes per instance
With __slots__¶
class Slotted:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
obj = Slotted(1, 2)
print(sys.getsizeof(obj)) # ~48 bytes (no __dict__)
Practical Examples¶
Compare Data Structure Efficiency¶
import sys
data = list(range(1000))
# Different representations
as_list = list(data)
as_tuple = tuple(data)
as_set = set(data)
as_frozenset = frozenset(data)
print(f"list: {sys.getsizeof(as_list):,} bytes")
print(f"tuple: {sys.getsizeof(as_tuple):,} bytes")
print(f"set: {sys.getsizeof(as_set):,} bytes")
print(f"frozenset: {sys.getsizeof(as_frozenset):,} bytes")
Memory Budget Check¶
import sys
def check_memory_budget(obj, max_bytes):
"""Check if object fits within memory budget."""
size = sys.getsizeof(obj)
if size > max_bytes:
raise MemoryError(
f"Object size {size:,} bytes exceeds budget {max_bytes:,} bytes"
)
return size
# Usage
cache = {}
check_memory_budget(cache, max_bytes=1024)
Profile Class Instances¶
import sys
def instance_memory_report(cls, *args, **kwargs):
"""Report memory usage of a class instance."""
obj = cls(*args, **kwargs)
base_size = sys.getsizeof(obj)
dict_size = sys.getsizeof(obj.__dict__) if hasattr(obj, '__dict__') else 0
print(f"Class: {cls.__name__}")
print(f" Base object: {base_size} bytes")
print(f" __dict__: {dict_size} bytes")
print(f" Total: {base_size + dict_size} bytes")
return obj
class MyClass:
def __init__(self, name, values):
self.name = name
self.values = values
instance_memory_report(MyClass, "test", [1, 2, 3])
Summary¶
| Type | Empty Size | Notes |
|---|---|---|
None |
16 bytes | Singleton |
int (small) |
24-28 bytes | Grows with magnitude |
float |
24 bytes | Fixed (64-bit) |
str |
49+ bytes | Varies by encoding |
list |
56+ bytes | Over-allocates |
tuple |
40+ bytes | More compact than list |
dict |
64+ bytes | Hash table overhead |
set |
216+ bytes | Hash table overhead |
object |
~48 bytes | Plus __dict__ (~104 bytes) |
Key Takeaways:
sys.getsizeof()returns shallow size only- Use recursive functions or
pymplerfor deep size - Empty containers have significant overhead
tupleis more memory-efficient thanlist__slots__reduces instance size by ~60-70%- String size depends on character encoding
- Integer size grows with magnitude (arbitrary precision)