Understanding how much memory your Python objects consume is essential for writing efficient code - especially when working with large datasets, optimizing performance, or debugging memory issues. Python provides the built-in sys.getsizeof() function to measure the memory size of any object in bytes.
This guide explains how to use sys.getsizeof(), how to interpret its results, and how to measure the total (deep) size of complex objects that contain references to other objects.
Using sys.getsizeof() for Basic Size Measurement
The sys.getsizeof() function is part of Python's sys module. It returns the size of an object in bytes, including the garbage collector overhead.
import sys
print(f"Integer (12): {sys.getsizeof(12)} bytes")
print(f"Float (3.14): {sys.getsizeof(3.14)} bytes")
print(f"String ('tutorial'): {sys.getsizeof('tutorial')} bytes")
print(f"Boolean (True): {sys.getsizeof(True)} bytes")
print(f"None: {sys.getsizeof(None)} bytes")Output:
Integer (12): 28 bytes
Float (3.14): 24 bytes
String ('tutorial'): 57 bytes
Boolean (True): 28 bytes
None: 16 bytesUnderstanding Python Object Sizes
Python objects have a base overhead plus a variable component that grows with the amount of data stored. Here's a reference table for common types (sizes may vary slightly between Python versions and platforms):
| Object Type | Base Size (Bytes) | Growth Pattern |
|---|---|---|
int |
28 | Grows for very large integers (arbitrary precision) |
float |
24 | Fixed size |
bool |
28 | Same as int (subclass of int) |
None |
16 | Fixed size |
str |
49 | +1 byte per additional character (for ASCII) |
bytes |
33 | +1 byte per additional byte |
tuple |
40 (empty) | +8 bytes per item |
list |
56 (empty) | +8 bytes per item |
set |
216 (empty) | Grows in steps (216 → 728 → 2264...) |
dict |
64 (empty) | Grows in steps (64 → 232 → 360...) |
function |
136 | Base size without attributes |
Measuring Container Sizes
Let's examine how the size changes as containers grow:
import sys
print("--- Tuples ---")
print(f"Empty tuple: {sys.getsizeof(())} bytes")
print(f"5-item tuple: {sys.getsizeof(('a', 'b', 'c', 'd', 'e'))} bytes")
print("\n--- Lists ---")
print(f"Empty list: {sys.getsizeof([])} bytes")
print(f"5-item list: {sys.getsizeof(['a', 'b', 'c', 'd', 'e'])} bytes")
print("\n--- Sets ---")
print(f"Empty set: {sys.getsizeof(set())} bytes")
print(f"4-item set: {sys.getsizeof({1, 2, 3, 4})} bytes")
print("\n--- Dicts ---")
print(f"Empty dict: {sys.getsizeof({}) } bytes")
print(f"4-item dict: {sys.getsizeof({1: 'a', 2: 'b', 3: 'c', 4: 'd'})} bytes")Output:
--- Tuples ---
Empty tuple: 40 bytes
5-item tuple: 80 bytes
--- Lists ---
Empty list: 56 bytes
5-item list: 104 bytes
--- Sets ---
Empty set: 216 bytes
4-item set: 216 bytes
--- Dicts ---
Empty dict: 64 bytes
4-item dict: 224 bytesHow Strings Grow
import sys
print(f"Empty string '': {sys.getsizeof('')} bytes")
print(f"1-char string 'a': {sys.getsizeof('a')} bytes")
print(f"5-char string 'hello': {sys.getsizeof('hello')} bytes")
print(f"10-char string: {sys.getsizeof('helloworld')} bytes")Output:
Empty string '': 49 bytes
1-char string 'a': 50 bytes
5-char string 'hello': 54 bytes
10-char string: 59 bytesEach additional ASCII character adds exactly 1 byte to the base size of 49 bytes.
Measuring Deep (Total) Size of Objects
For nested or complex objects, you need a recursive function that traverses all referenced objects. Here's a reusable deep size calculator:
import sys
def deep_getsizeof(obj, seen=None):
"""Recursively calculate the total memory size of an 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)
return size
data = {
"name": "Alice",
"scores": [95, 87, 92, 88],
"details": {"city": "Delhi", "active": True}
}
print(f"Shallow size: {sys.getsizeof(data)} bytes")
print(f"Deep size: {deep_getsizeof(data)} bytes")Output:
Shallow size: 184 bytes
Deep size: 976 byteThe deep size is nearly 3x larger because it accounts for the strings, integers, nested list, and nested dictionary - all of which the shallow measurement ignores.
Comparing Sizes of Different Data Structures
Understanding relative sizes helps you choose the right data structure:
import sys
items_tuple = (1, 2, 3, 4, 5)
items_list = [1, 2, 3, 4, 5]
items_set = {1, 2, 3, 4, 5}
items_dict = {1: "a", 2: "b", 3: "c", 4: "d", 5: "e"}
print(f"Tuple (5 items): {sys.getsizeof(items_tuple):>6} bytes")
print(f"List (5 items): {sys.getsizeof(items_list):>6} bytes")
print(f"Set (5 items): {sys.getsizeof(items_set):>6} bytes")
print(f"Dict (5 items): {sys.getsizeof(items_dict):>6} bytes")Output:
Tuple (5 items): 80 bytes
List (5 items): 104 bytes
Set (5 items): 472 bytes
Dict (5 items): 224 bytesKey takeaways:
- Tuples are the most memory-efficient sequence type (immutable, no over-allocation).
- Lists use slightly more memory than tuples due to dynamic resizing overhead.
- Sets and dicts use significantly more memory because of their hash table implementation - but they provide O(1) lookups.
Measuring the Size of Custom Objects
You can also measure the size of class instances:
import sys
class User:
def __init__(self, name, age):
self.name = name
self.age = age
class UserSlots:
__slots__ = ["name", "age"]
def __init__(self, name, age):
self.name = name
self.age = age
user_regular = User("Alice", 30)
user_slots = UserSlots("Alice", 30)
print(f"Regular class instance: {sys.getsizeof(user_regular)} bytes")
print(f"__slots__ class instance: {sys.getsizeof(user_slots)} bytes")Output:
Regular class instance: 56 bytes
__slots__ class instance: 48 bytesUsing pympler for Advanced Memory Profiling
For more accurate and detailed memory analysis, the third-party pympler library provides asizeof(), which automatically performs deep size measurement:
pip install pymplerfrom pympler import asizeof
data = {"name": "Alice", "scores": [95, 87, 92, 88]}
print(f"pympler deep size: {asizeof.asizeof(data)} bytes")This gives you a single function call that handles all the recursive traversal automatically.
Quick Reference
| Function | Measures | Depth | Source |
|---|---|---|---|
sys.getsizeof(obj) |
Direct object memory | Shallow only | Standard library |
Custom deep_getsizeof() |
Object + all referenced objects | Deep (recursive) | Manual implementation |
pympler.asizeof.asizeof() |
Object + all referenced objects | Deep (automatic) | Third-party (pympler) |
Conclusion
Measuring object sizes in Python is straightforward with sys.getsizeof(), but understanding its limitations is critical:
sys.getsizeof()measures only the shallow size - the object's own memory footprint, excluding the objects it references.- For containers like lists, dicts, and nested structures, use a recursive deep size function or the
pymplerlibrary to get the true total memory consumption. - Python objects are larger than their raw data because every object carries metadata (type, reference count, etc.).
- Tuples are more memory-efficient than lists, and
__slots__classes are more efficient than regular classes when creating many instances.
Understanding these details helps you make informed decisions about data structures and optimize memory usage in your Python applications.