Skip to main content

How to Measure Object Size Using getsizeof() and sizeof() methods in Python

Understanding memory consumption is essential for optimizing Python applications, especially when working with large datasets or memory-constrained environments. Python provides two primary methods for measuring object size: sys.getsizeof() and the __sizeof__() method. While they appear similar, they measure subtly different things.

This guide explains both approaches, their differences, and the important limitations you need to understand for accurate memory profiling.

Using sys.getsizeof()

The sys.getsizeof() function is the standard and recommended way to measure an object's memory footprint. It returns the size in bytes, including the garbage collector overhead required by Python's memory management system:

import sys

# Measure various object types
my_list = [1, 2, 3, 4, 5]
my_dict = {"name": "Alice", "age": 30}
my_string = "Hello, World!"

print(f"List size: {sys.getsizeof(my_list)} bytes") # List size: 104 bytes
print(f"Dict size: {sys.getsizeof(my_dict)} bytes") # Dict size: 184 bytes
print(f"String size: {sys.getsizeof(my_string)} bytes") # String size: 62 bytes

You can also provide a default value for objects that don't support size measurement:

# Returns default if object doesn't support sizeof
size = sys.getsizeof(some_object, default=0)
Garbage Collector Overhead

sys.getsizeof() includes 16-32 bytes of additional overhead used by Python's garbage collector to track and manage objects. This represents the true memory cost to your application.

Using the sizeof() Method

The __sizeof__() method returns the object's base size as defined by its C implementation, excluding garbage collector overhead:

import sys

data = [10, 20, 30]

# Compare both measurements
gc_size = sys.getsizeof(data)
base_size = data.__sizeof__()

print(f"With GC overhead: {gc_size} bytes") # With GC overhead: 88 bytes
print(f"Base size only: {base_size} bytes") # Base size only: 72 bytes
print(f"GC overhead: {gc_size - base_size} bytes") # GC overhead: 16 bytes
Limited Availability

While built-in types implement __sizeof__(), custom classes may not. Always use sys.getsizeof() for reliable measurements across all object types.

The Shallow Size Limitation

Both methods measure only shallow size: the container's memory, not its contents. This is a critical concept to understand:

import sys

# These lists have vastly different content sizes
small_items = [1, 2, 3]
large_items = ["x" * 1000000, "y" * 1000000, "z" * 1000000]

print(f"Small items list: {sys.getsizeof(small_items)} bytes") # Small items list: 88 bytes
print(f"Large items list: {sys.getsizeof(large_items)} bytes") # Large items list: 80 bytes
# Both return similar sizes despite huge content difference!

The sizes appear similar because lists store pointers to objects, not the objects themselves. Each pointer is typically 8 bytes regardless of what it points to.

Calculating Deep Size

To measure total memory including nested objects, use a recursive approach:

import sys

def deep_getsizeof(obj, seen=None):
"""Recursively calculate total size of object and 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)

return size


# Compare shallow vs deep measurement
nested_data = {
"users": ["Alice", "Bob", "Charlie"],
"scores": [95, 87, 92],
"metadata": {"version": "1.0", "count": 3}
}

print(f"Shallow size: {sys.getsizeof(nested_data)} bytes") # Shallow size: 184 bytes
print(f"Deep size: {deep_getsizeof(nested_data)} bytes") # Deep size: 1146 bytes

Method Comparison

Featuresys.getsizeof()__sizeof__()
GC overheadIncludedExcluded
ReliabilityWorks on all objectsMay not be implemented
AccuracyReal-world footprintImplementation detail
Measurement depthShallowShallow

Practical Memory Profiling

For comprehensive memory analysis, consider these approaches:

import sys

def deep_getsizeof(obj, seen=None):
... # as defined in example above

def format_size(bytes_size):
"""Convert bytes to human-readable format."""
for unit in ['B', 'KB', 'MB', 'GB']:
if bytes_size < 1024:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024
return f"{bytes_size:.2f} TB"

def profile_object(obj, name="Object"):
"""Display memory profile for an object."""
shallow = sys.getsizeof(obj)
deep = deep_getsizeof(obj)

print(f"{name}:")
print(f" Shallow: {format_size(shallow)}")
print(f" Deep: {format_size(deep)}")
print(f" Ratio: {deep/shallow:.1f}x")


# Profile different data structures
profile_object([i for i in range(1000)], "List of 1000 integers")
profile_object({i: str(i) for i in range(1000)}, "Dict with 1000 items")

Output:

List of 1000 integers:
Shallow: 8.65 KB
Deep: 35.99 KB
Ratio: 4.2x
Dict with 1000 items:
Shallow: 36.09 KB
Deep: 114.10 KB
Ratio: 3.2x
Architecture Matters

Object sizes vary between 32-bit and 64-bit Python installations because pointer sizes differ. Always profile on your target deployment architecture for accurate measurements.

Understanding the distinction between shallow and deep size, and choosing the appropriate measurement method, enables accurate memory profiling and effective optimization of your Python applications.