The weakref module creates references to objects that don't prevent garbage collection. When you need to reference an object without keeping it alive, weak references are the answer.

The Problem

Normal references keep objects alive:

class ExpensiveObject:
    def __init__(self, data):
        self.data = data
 
cache = {}
obj = ExpensiveObject("large data")
cache['key'] = obj  # Strong reference in cache
del obj  # Object still alive because cache holds reference

The object can't be garbage collected until removed from the cache.

Weak References

Weak references don't prevent collection:

import weakref
 
class ExpensiveObject:
    def __init__(self, data):
        self.data = data
 
obj = ExpensiveObject("large data")
weak_ref = weakref.ref(obj)
 
# Access the object
print(weak_ref())  # <ExpensiveObject object>
 
del obj  # Now it can be garbage collected
print(weak_ref())  # None

WeakValueDictionary

A dictionary that doesn't keep values alive:

import weakref
 
class User:
    def __init__(self, name):
        self.name = name
 
cache = weakref.WeakValueDictionary()
 
def get_user(user_id):
    if user_id in cache:
        return cache[user_id]
    user = User(f"User {user_id}")
    cache[user_id] = user
    return user
 
u = get_user(1)
print(len(cache))  # 1
 
del u  # User can be collected
# After GC, cache[1] disappears automatically

WeakKeyDictionary

Store data associated with objects without keeping them alive:

import weakref
 
class Document:
    pass
 
metadata = weakref.WeakKeyDictionary()
 
doc = Document()
metadata[doc] = {"views": 100, "modified": True}
 
# Access metadata while doc exists
print(metadata[doc])
 
del doc  # Document collected, metadata entry removed

WeakSet

A set that doesn't keep members alive:

import weakref
 
class Observer:
    def __init__(self, name):
        self.name = name
 
observers = weakref.WeakSet()
 
o1 = Observer("first")
o2 = Observer("second")
 
observers.add(o1)
observers.add(o2)
 
print(len(observers))  # 2
 
del o1
# After GC, observers only contains o2

Callbacks on Collection

Run code when an object is collected:

import weakref
 
class Resource:
    def __init__(self, name):
        self.name = name
 
def on_collected(ref):
    print(f"Resource was collected")
 
obj = Resource("important")
weak_ref = weakref.ref(obj, on_collected)
 
del obj  # Prints: Resource was collected

Practical Example: Caching

Cache expensive computations without memory leaks:

import weakref
 
class ImageProcessor:
    _cache = weakref.WeakValueDictionary()
    
    @classmethod
    def get_processed(cls, image_id):
        if image_id in cls._cache:
            return cls._cache[image_id]
        
        # Expensive processing
        result = cls._process(image_id)
        cls._cache[image_id] = result
        return result
    
    @classmethod
    def _process(cls, image_id):
        # Simulate expensive work
        return ProcessedImage(image_id)

When ProcessedImage objects are no longer used elsewhere, they're automatically removed from the cache.

Observer Pattern

Implement observers without memory leaks:

import weakref
 
class Subject:
    def __init__(self):
        self._observers = weakref.WeakSet()
    
    def attach(self, observer):
        self._observers.add(observer)
    
    def notify(self, message):
        for observer in self._observers:
            observer.update(message)
 
class Observer:
    def update(self, message):
        print(f"Received: {message}")
 
subject = Subject()
obs = Observer()
subject.attach(obs)
 
subject.notify("hello")  # Works
 
del obs
subject.notify("world")  # obs is gone, no notification sent

Finalize

Run cleanup code when objects are collected:

import weakref
 
class TempFile:
    def __init__(self, path):
        self.path = path
        self._finalizer = weakref.finalize(
            self, self._cleanup, path
        )
    
    @staticmethod
    def _cleanup(path):
        print(f"Cleaning up {path}")
        # os.unlink(path)
 
tmp = TempFile("/tmp/data.txt")
del tmp  # Prints: Cleaning up /tmp/data.txt

Limitations

Not all objects support weak references:

import weakref
 
# These work
weakref.ref(object())
weakref.ref([])  # Error! list doesn't support weakref
 
# Built-in types like int, str, list, dict, tuple
# don't support weak references by default

Custom classes support weak references unless __slots__ excludes __weakref__.

When to Use weakref

Use weak references when:

  • Building caches that shouldn't prevent GC
  • Implementing observer patterns
  • Storing metadata about objects you don't own
  • Breaking reference cycles

Don't use them when:

  • You need to guarantee the object stays alive
  • Working with built-in types that don't support them
  • The complexity isn't worth the memory savings

Weak references are a specialized tool for memory management. When you need objects to be collectible while still being referenceable, they're exactly what you need.

React to this post: