TechSchool
Home / Python3 / Python Memory Management

Python Memory Management

This page explains how Python manages memory and how to find memory leaks. It shows the tools and patterns that keep your program small and fast.

Names, Identity, and Reference Counts

A name is a reference to an object. The function id() shows the identity of an object. Two names that point to the same object have the same id.

Python counts the references to each object. When the count reaches zero, Python frees the object. The function sys.getrefcount shows the count for an object.

The call sys.getrefcount(x) returns one more than you expect. The argument itself is a temporary reference. This extra reference exists only during the call.

import sys

a = []
b = a
print(id(a) == id(b))
print(sys.getrefcount(a))
del b
print(sys.getrefcount(a))

Cyclic Garbage Collection

Reference counting cannot free a reference cycle. A cycle occurs when objects refer to each other. The cyclic garbage collector finds and frees these cycles.

The function gc.collect() runs a collection now. The function gc.get_count() shows the current count of tracked objects. The function gc.generation shows the generation of an object.

The collector uses three generations. Young objects are checked more often than old objects. This design keeps the cost low.

import gc

class Node:
    pass

x = Node()
x.self = x
del x
print(gc.collect())
print(gc.get_count())

Small Object Allocation and Interning

CPython uses pymalloc arenas for small objects. An arena is a block of memory that holds many small objects. This method makes small allocations fast.

Small integers and some strings are interned. Interning means that Python reuses one object for equal values. The identity check a is b can be true for interned values.

Do not rely on interning for your logic. Use == to compare values. Use is only to compare with None or True.

Common Memory Leak Patterns

A global cache that never shrinks keeps objects alive. The fix is to set a maximum size or to use weakref. A closure that keeps a large object alive is another pattern. The fix is to delete the closure or to avoid the capture.

A reference cycle with __del__ can delay collection. The fix is to remove __del__ or to use a weakref callback. Unclosed files and sockets hold resources. The fix is to use a with statement.

Objects kept in module-level lists grow without limit. The fix is to clear the list or to use a bounded structure. Check these patterns when memory grows.

Measure Memory with tracemalloc

The module tracemalloc measures memory allocations. Start it before the code that you want to check. Take a snapshot and compare snapshots to find growth.

The example below allocates a list and shows the top allocation. The output shows the file and the line that allocated the memory. Use this data to find the leak.

import tracemalloc

tracemalloc.start()
data = [str(i) for i in range(10000)]
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:3]:
    print(stat)
tracemalloc.stop()

Reduce Memory with __slots__ and weakref

A normal instance stores its attributes in a dictionary. The dictionary uses memory for each instance. The __slots__ attribute removes the dictionary and stores attributes in a fixed array.

A weakref does not increase the reference count. A cache that uses weakref does not hold objects alive. The object is freed when no strong reference remains.

The example shows a class with __slots__ and a weakref cache. The cache returns None after the strong reference is deleted. This pattern keeps memory small.

import weakref

class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2)
cache = weakref.WeakValueDictionary()
cache['p'] = p
del p
print(cache.get('p'))

Common questions

Why does sys.getrefcount return one more than expected?

The function call creates a temporary reference to its argument. This reference adds one to the count during the call.

Does del free memory at once?

The del statement removes a name and decreases the reference count. The object is freed only when the count reaches zero.

Why does a leak show up only under load?

A leak grows with each request or allocation. Low load hides the growth because the total memory stays small.

Continue learning
Python3 class Python3 func memoryview Python3 module