Memory Leak Examples in Python Programs

Explore practical examples of memory leaks in Python programs and learn how to identify and fix them.
By Jamie

Understanding Memory Leaks in Python

Memory leaks occur when a program allocates memory but fails to release it after use. This can lead to increased memory consumption over time, potentially causing the application to slow down or crash. In Python, memory management is primarily handled by the garbage collector, but certain coding patterns can still lead to memory leaks. Here are three diverse examples of memory leaks in Python programs.

Example 1: Unintentional Global Variable Usage

In this example, we examine how global variables can inadvertently cause memory leaks by retaining references to objects longer than necessary.

Global variables are accessible throughout the module, but if they hold onto large data structures, they can prevent the garbage collector from reclaiming that memory. This is especially problematic in long-running applications or services.

class MemoryLeak:
    def __init__(self):
        self.data = [i for i in range(10**6)]  # Large list

leak = MemoryLeak()  # `leak` holds a reference to the instance

# At this point, the memory for `self.data` is still allocated.

In this case, the MemoryLeak instance and its data list remain in memory as long as the reference leak exists, leading to a memory leak. To resolve this, ensure to delete references when they are no longer needed:

del leak

Example 2: Circular References with Custom Objects

This example illustrates how circular references can create memory leaks in Python. When two or more objects reference each other, the garbage collector may not be able to reclaim that memory, even if there are no external references to these objects. This is common when using custom objects.

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

node1 = Node(1)
node2 = Node(2)
node1.next = node2  # node1 references node2
node2.next = node1  # node2 references node1

# At this point, neither node1 nor node2 can be cleared by garbage collection

In this situation, to avoid memory leaks, you might want to implement a method to break the circular reference:

node1.next = None
node2.next = None

Example 3: Using Closures with Mutable Defaults

The next example demonstrates how using mutable default arguments in closures can lead to memory leaks. This issue arises when the mutable object, such as a list or dictionary, is modified and retains its state across function calls.

def add_to_list(value, my_list=[]):
    my_list.append(value)
    return my_list

print(add_to_list(1))  # Output: [1]
print(add_to_list(2))  # Output: [1, 2]  # Memory leak occurs here

In this case, the default list my_list retains previous values, leading to unexpected behavior and potential memory issues. To prevent this, use None as the default value and create a new list inside the function:

def add_to_list(value, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

By recognizing these common patterns, developers can write more memory-efficient Python code and reduce the risk of memory leaks.