Automatic memory management

What is nice in Python, but also in Java, Javascript, Lisp, OCaml etc.: we do not have to explicit free the memory occupied by unused objects. It is automatic!

Reference counting

import sys
x = [1, 2]
y = x
sys.getrefcount(x)
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Cell In[23], line 4
      2 x = [1, 2]
      3 y = x
----> 4 x.__weakref__
      5 sys.getrefcount(x)


AttributeError: 'list' object has no attribute '__weakref__'
def f():
    x = [1, 2]

Reference counting in CPython

See https://github.com/python/cpython/blob/3.12/Include/object.h

When a reference is created, we increment the reference counter.

/* Function to use in case the object pointer can be NULL: */
static inline void Py_XINCREF(PyObject *op)
{
    if (op != _Py_NULL) {
        Py_INCREF(op);
    }
}

When a variable is destroyed, the reference counter is decremented. Objects with reference counting 0 are destroyed.

static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
    if (op->ob_refcnt <= 0) {
        _Py_NegativeRefcount(filename, lineno, op);
    }
    if (_Py_IsImmortal(op)) {
        return;
    }
    _Py_DECREF_STAT_INC();
    _Py_DECREF_DecRefTotal();
    if (--op->ob_refcnt == 0) {
        _Py_Dealloc(op);
    }
}

Explicit deletion of a placeholder

In Python, del is destroying a variable name. It is not destroying an object, it just decrements the reference counter.

x = 2
del x

Limitation: Cyclic references

Reference counting is nice but not sufficient for freeing memory.

class Node:
    def __init__(self):
        self.friend = None

def f():
    A = Node()
    B = Node()
    A.friend = B
    B.friend = A

f()

Garbage collection

Garbage collection consists in freeing the memory occupied by unused objects, even when reference counting is non-zero. The garbage collector is sometimes called and performs the following algorithm called mark-and-sweep:

  • mark. DFS from variables to mark reachable objects
  • sweep. Free the memory of unmarked objects.

Optimisation

CPython tends to call garbage collection less frequently on old objects.

import gc

# you can call the garbage collection directly like this, but I do not see the point...
gc.collect()

Weak references

A weak reference is a reference that leaves the reference counter as it is. A weak reference does not own the object.

import sys
import weakref

class Person:
    pass

x = Person()
y = weakref.ref(x)

print(y)
print(y())
sys.getrefcount(x)
<weakref at 0x7f730681aa40; to 'Person' at 0x7f7307588970>
<__main__.Person object at 0x7f7307588970>





2
import sys
import weakref

class Person:
    pass

x = Person()
y = weakref.ref(x)
del x

print(y)
print(y())
<weakref at 0x7f73067cba90; dead>
None

The concept of weak reference is similar to std::weak_ptr in C++, and Weak<T> in Rust.