---
title: "Python: Memory Management"
description: "To write better code in Python, you need to have a good understanding of this topic."
canonical_url: "https://otabek.io/blogs/python-memory-management"
md_url: "https://otabek.io/blogs/python-memory-management.md"
language: "en"
last_updated: "2024-07-03"
tags: ["Python"]
---

# Python: Memory Management

If you want to write better code in Python, it's important to understand this topic well.

> This post might have some inaccuracies. Please feel free to research and explore on your own!

## Garbage Collection

![](https://telegra.ph/file/2e8f52e17018ab17745eb.jpg)

In English, "Garbage Collection" is the process of collecting unreferenced objects that are no longer in use. But when can something be considered garbage? It's simple—when you can no longer use an object or value, you consider it garbage. This process exists in programming languages as well. When a value or object is no longer usable, the **Garbage Collector** (GC) takes it and removes it from memory (deallocates it).

But why do we need a **Garbage Collector**? Without it, memory would fill up quickly. If you don't want your computer's memory to look like the image above, then the Garbage Collector is essential 🙃.

## Reference Count

![](https://telegra.ph/file/1863988454ce5bc97866e.png)

As humans, it's easy to understand garbage. You buy a bottle of water, drink it, and know that the bottle is no longer useful to you—it's now garbage. In programming, after a value or object is created, its **reference count** is calculated, which means how many variables are pointing to it. For example:

```python-run
# An object 1 is created in memory, but the computer doesn't know how to get it, so it deletes it immediately
1
# Variable x points to 1 in memory
x = 1
# x holds the memory address and when you print x, it will show 1
print(x)
# When you delete the variable, the reference to 1 is gone, and 1 becomes garbage.
del x
```

You might think **variables** store values, but they actually just provide references to those values. When you run `print(x)`, `x` doesn’t give the value directly; it just tells where to find that value in memory, and the program fetches it for you. So from now on, think of variables as **references**.

The `sys` library is used for system-related tasks in Python. The function `sys.getrefcount()` can show how many references an object has. However, this function always returns one extra reference count because it also points to the object to get the reference count. So, subtract 1 from the result to get the actual count.

```python-run
import sys

x = [4, 3, 2, 1]
sys.getrefcount(x) # 2

y = x
sys.getrefcount(y) # 3

z = y
sys.getrefcount(z) # 4

# If you modify z, both x and y will change because they all point to the same object.
z.append(0)
print(x, y, z, sep="\n")
# [4, 3, 2, 1, 0]
# [4, 3, 2, 1, 0]
# [4, 3, 2, 1, 0]

# If you set z to a new value or delete it:
z = 1
# or
del z
sys.getrefcount(y) # 3
```

Did you know that Python compares two objects in constant time, O(1)? Yes, you heard that right. Python doesn't compare the values of objects; it compares their memory addresses. That's it!

```python-run
a = 1
b = 1
print(a is b)  # True

c = 257
d = 257
print(c is d)  # False

m = "abc"
n = "abc"
print(m is n)  # True

x = "long string example"
y = "long string example"
print(x is y)  # True
```

```python-run
import sys

a = 10
b = a
print(sys.getrefcount(a) - 1)  # 2
```

```python-run
import sys

a = 10
b = a
print(sys.getrefcount(a) - 1)  # 2

b = 20
print(sys.getrefcount(a) - 1)  # 1
```

## Finalizer

![](https://telegra.ph/file/7a830856c0fa2c1170a64.png)

A **Finalizer** is a mechanism that allows you to define cleanup actions to be performed when an object is destroyed (by the Garbage Collector). This can be done using the `__del__` method or the `weakref.finalize` function. In simple terms, the `del object` statement triggers this process. However, Python doesn't guarantee that the object will be deleted when you use `del`. [[python doc](https://docs.python.org/3/reference/datamodel.html#object.__del__ 'source')]

```python-run
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None

x = Node(1)
y = Node(2)

x.next = y
y.next = x
```

Objects can reference each other, as shown in the code above, especially in data structures like **Linked Lists**, **Graphs**, or similar. This situation is known as **cyclic reference**. The Garbage Collector might struggle to detect and delete these cyclic references, which can lead to a [memory leak](https://en.wikipedia.org/wiki/Memory_leak). To handle this, you can use either the `__dict__` method or the `weakref.finalize()` function. The `obj.__dict__` method provides all the attributes of an object, allowing you to delete them automatically. CPython typically follows the `__dict__` approach.

One of the worst cases of cyclic references happens when a list appends itself, creating an infinite loop. Initially, it might appear to have only 8 items, but as you dig deeper, the number keeps growing.

```python-run
arr = []
arr.append(arr)
```

```python-run
import ctypes
import gc

class PyObject(ctypes.Structure):
    _fields_ = [("refcnt", ctypes.c_long)]

gc.disable()  # Disable garbage collector
a = "Hello World"
address_of_a = id(a)

# Get reference count
print(PyObject.from_address(address_of_a).refcnt)  # 2

b = a
print(PyObject.from_address(address_of_a).refcnt)  # 3

c = a
print(PyObject.from_address(address_of_a).refcnt)  # 4

c = 10
print(PyObject.from_address(address_of_a).refcnt)  # 3

b = 10
print(PyObject.from_address(address_of_a).refcnt)  # 2

gc.enable()  # Re-enable garbage collector
```

## Conclusion

![Stack](https://telegra.ph/file/8b92a3cceb9f4a6777122.jpg)

As seen in the error message above, you might be curious about why such an error occurs. Python uses a **Stack** data structure for code execution. The golden rule of the Stack is LIFO (Last In, First Out). When the code is executed, it pops the last item from the stack, and if any error occurs, it shows the sequence to help debug. Quite interesting, right? 😉

Additionally, research **weak references**, **Garbage collector graph traversal algorithms**, and **Stack** in Python, as they play crucial roles in memory management. I hope this post has been helpful in your understanding of Python's memory management.

If you find any errors in this post, feel free to contact me: <a href="mailto:ask@otabek.io">ask@otabek.io</a>

---

```quiz
{
  "quiz": {
    "id": "python-memory-quiz",
    "title": "Memory Management Quiz",
    "description": "Test your understanding of Python memory concepts",
    "questions": [
      {
        "id": "q1",
        "type": "single-choice",
        "question": "What does the Garbage Collector do in Python?",
        "options": [
          { "id": "a", "text": "Removes unreferenced objects from memory", "description": "" },
          { "id": "b", "text": "Compiles Python code to bytecode", "description": "The compiler handles bytecode generation, not the GC." },
          { "id": "c", "text": "Manages CPU usage", "description": "GC deals with memory, not CPU management." },
          { "id": "d", "text": "Stores variables in cache", "description": "GC removes objects, it doesn't store them." }
        ]
      },
      {
        "id": "q2",
        "type": "single-choice",
        "question": "What do variables actually store in Python?",
        "options": [
          { "id": "a", "text": "The actual values directly", "description": "Variables don't store values - they reference memory locations where values are stored." },
          { "id": "b", "text": "References (memory addresses) to values", "description": "" },
          { "id": "c", "text": "Copies of the values", "description": "Python doesn't automatically copy values when assigning variables." },
          { "id": "d", "text": "Encrypted data", "description": "Python variables are simple references, no encryption involved." }
        ]
      },
      {
        "id": "q3",
        "type": "drag-fill",
        "question": "Complete the code to get the reference count of variable x:",
        "template": "import {{b1}}\n\nx = [1, 2, 3]\ncount = {{b2}}.{{b3}}(x)",
        "options": [
          { "id": "opt1", "content": "sys" },
          { "id": "opt2", "content": "sys" },
          { "id": "opt3", "content": "getrefcount" }
        ],
        "blanks": [
          { "id": "b1" },
          { "id": "b2" },
          { "id": "b3" }
        ]
      },
      {
        "id": "q4",
        "type": "single-choice",
        "question": "What is a 'cyclic reference' in Python?",
        "options": [
          { "id": "a", "text": "When objects reference each other in a loop", "description": "" },
          { "id": "b", "text": "When a variable is used in a for loop", "description": "Cyclic reference is about mutual object references, not loop iteration." },
          { "id": "c", "text": "When memory is recycled", "description": "Recycling memory is garbage collection, not cyclic reference." },
          { "id": "d", "text": "When a function calls itself", "description": "That's recursion, not cyclic reference." }
        ]
      },
      {
        "id": "q5",
        "type": "multiple-choice",
        "question": "Which of these can cause memory leaks in Python? (Select all that apply)",
        "options": [
          { "id": "a", "text": "Cyclic references between objects", "description": "" },
          { "id": "b", "text": "Creating simple integer variables", "description": "Simple variables are easily garbage collected and don't cause leaks." },
          { "id": "c", "text": "A list that appends itself", "description": "" },
          { "id": "d", "text": "Printing to console", "description": "Print statements don't create persistent memory issues." }
        ]
      },
      {
        "id": "q6",
        "type": "drag-drop",
        "question": "Arrange what happens when you run: x = [1,2,3]; y = x; del x",
        "items": [
          { "id": "step1", "content": "List [1,2,3] created in memory" },
          { "id": "step2", "content": "x references the list (refcount=1)" },
          { "id": "step3", "content": "y also references the list (refcount=2)" },
          { "id": "step4", "content": "del x removes x reference (refcount=1)" },
          { "id": "step5", "content": "List still exists because y references it" }
        ]
      }
    ]
  },
  "answers": {
    "q1": { "correctOptionIds": ["a"] },
    "q2": { "correctOptionIds": ["b"] },
    "q3": { "correctPlacements": { "b1": "opt1", "b2": "opt2", "b3": "opt3" } },
    "q4": { "correctOptionIds": ["a"] },
    "q5": { "correctOptionIds": ["a", "c"] },
    "q6": { "correctOrder": ["step1", "step2", "step3", "step4", "step5"] }
  }
}
```


## Sitemap

See the full [Markdown sitemap](/sitemap.md) for all pages.
