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.