Python weakref: Weak References

Published on: July 28, 2026
Reading time: 8 minutes
Memory module representing weak references and caches in Python

Automatic memory management is one of Python’s greatest conveniences, but ordinary references can keep objects alive longer than an application needs them. A cache, observer registry, or metadata mapping may retain large objects after the rest of the program has stopped using them. The Python weakref module provides weak references: links that can access an object while it exists without preventing garbage collection from reclaiming its memory.

This guide covers strong and weak references, weakref.ref(), WeakValueDictionary, WeakKeyDictionary, WeakSet, WeakMethod, proxies, and finalize(). It complements the guides about Python descriptors, fixing Python MemoryError, the collections module, diagnosing slow Python scripts, and lazy iteration and generator expressions.

Strong references and object lifetime

A normal variable holds a strong reference. As long as at least one strong reference remains, the object stays alive. Variables, attributes, list entries, and dictionary values all commonly create strong references.

class Image:
    def __init__(self, name):
        self.name = name

image = Image("cover.png")
cache = {"cover": image}
del image

print(cache["cover"].name)

Deleting the local variable does not destroy the object because the dictionary still owns it. This is usually correct, but it is undesirable when the mapping is only an optional cache or auxiliary index. A weak reference does not increase the strong-reference count. When only weak references remain, the object becomes eligible for collection.

Creating a reference with weakref.ref

weakref.ref() returns a callable reference object. Calling it returns the referent while it is alive or None after collection.

import weakref

class Document:
    pass

document = Document()
reference = weakref.ref(document)

print(reference() is document)
del document
print(reference())

The official Python weakref documentation uses the word referent for the object targeted by a weak reference. The key property is that the weak reference does not extend the referent’s lifetime.

The safe dereferencing pattern

Do not test a weak reference and then call it again. In concurrent code, another thread could remove the last strong reference between those operations. Retrieve the object once and keep the local strong reference during use.

obj = reference()
if obj is None:
    print("The object is gone")
else:
    obj.process()

Assigning the result to obj temporarily keeps the referent alive. This pattern is safe in single-threaded and threaded code.

Callbacks after collection

weakref.ref() accepts a callback that runs when the referent is about to be finalized. The callback receives the weak-reference object, not the unavailable referent.

import weakref

class Session:
    pass

def removed(reference):
    print("Session removed from the index")

session = Session()
reference = weakref.ref(session, removed)
del session

Do not capture the monitored object through a closure, argument, or bound method. That indirect strong reference would prevent collection. Exceptions raised by weak-reference callbacks are written to standard error but cannot propagate to the code that released the final strong reference.

Automatic caches with WeakValueDictionary

WeakValueDictionary stores ordinary keys and weak values. When a value has no strong references elsewhere, its entry disappears automatically.

from weakref import WeakValueDictionary

class Profile:
    def __init__(self, identifier):
        self.identifier = identifier

_cache = WeakValueDictionary()

def load_profile(identifier):
    profile = _cache.get(identifier)
    if profile is None:
        profile = Profile(identifier)
        _cache[identifier] = profile
    return profile

profile = load_profile(42)
print(list(_cache))
del profile
print(list(_cache))

This pattern suits expensive objects that can be reconstructed: decoded images, temporary models, resource wrappers, syntax trees, and intermediate representations. The cache is an optimization, not persistent storage, because an entry may vanish whenever no caller owns the object.

When a weak cache is not enough

A weak cache can empty immediately if callers do not retain values. It also provides no maximum size, least-recently-used policy, expiration time, or distributed consistency. functools.lru_cache() may be better for small deterministic results. Databases and external caches are appropriate when data must survive object collection or process restarts.

Choose WeakValueDictionary when the rule is: reuse this object while another part of the program genuinely needs it, but never keep it alive only because it appears in the cache.

External metadata with WeakKeyDictionary

WeakKeyDictionary stores keys weakly. It lets a library associate information with third-party objects without changing those objects and without taking ownership of their lifetime.

from weakref import WeakKeyDictionary

class Connection:
    pass

metrics = WeakKeyDictionary()
connection = Connection()
metrics[connection] = {"queries": 3}

print(metrics[connection])
del connection
print(len(metrics))

This is valuable for instrumentation, descriptors, frameworks, adapters, and plugins. When the primary instance disappears, its auxiliary metadata is removed as well.

Equality and identity in weak keys

A subtle behavior appears when two different key objects compare equal. Inserting the second key may replace the value without replacing the internally retained identity of the first key. When the original key is collected, the entry can disappear even though the second equal object remains alive.

If distinct but equal instances are common, delete the old entry before assigning the new one or use a stable immutable identifier. Classes that override equality and hashing should include explicit tests for weak-key behavior.

Observer registries with WeakSet

WeakSet implements the set interface while keeping elements weakly. It is a natural fit for observer, listener, and active-object registries.

from weakref import WeakSet

class Observer:
    def update(self, event):
        print("Event:", event)

observers = WeakSet()
observer = Observer()
observers.add(observer)

for item in list(observers):
    item.update("data changed")

del observer
print(len(observers))

Iterating over a snapshot can be useful when callbacks may alter the registry. A weak registry prevents the publisher from accidentally becoming the owner of every subscriber.

Bound methods and WeakMethod

A bound method such as instance.refresh is a temporary object created during attribute access. A regular weak reference to it may expire immediately. WeakMethod stores enough information to recreate the bound method while both the instance and original function are alive.

from weakref import WeakMethod

class View:
    def refresh(self):
        print("Refreshing")

view = View()
callback = WeakMethod(view.refresh)

method = callback()
if method is not None:
    method()

del view
print(callback())

This is especially helpful in event systems, graphical interfaces, and message buses that should not retain consumers forever.

Weak proxies

weakref.proxy() creates an object that forwards most operations to the referent. It avoids explicit calls to the reference object, but access after collection raises ReferenceError.

import weakref

class Settings:
    environment = "production"

settings = Settings()
proxy = weakref.proxy(settings)
print(proxy.environment)

del settings

try:
    print(proxy.environment)
except ReferenceError:
    print("Settings are unavailable")

Proxies are never hashable, even when the referent is. For library APIs, explicit references often make the missing-object state clearer and easier to type-check.

Cleanup with weakref.finalize

weakref.finalize() registers a cleanup callable that runs at most once when an object is collected. The finalizer object remains alive automatically, making it easier to manage than a raw weak-reference callback.

import shutil
import tempfile
import weakref

class TemporaryDirectory:
    def __init__(self):
        self.path = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(
            self,
            shutil.rmtree,
            self.path,
            ignore_errors=True,
        )

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive

The finalizer can be invoked explicitly, and later calls do nothing. By default, remaining live finalizers also run during normal interpreter shutdown in reverse creation order.

Do not retain the monitored object

The function, positional arguments, and keyword arguments passed to finalize() must not own the monitored object directly or indirectly. Passing the instance’s own bound method is a common mistake.

# Avoid:
# weakref.finalize(self, self.close)

# Prefer an external function with only required data:
weakref.finalize(self, close_resource, resource_id)

If the finalizer keeps self, the instance owns the finalizer, the finalizer owns the bound method, and the method owns the instance. The object may never become collectible.

Types that support weak references

Instances of normal Python classes generally support weak references. Python functions, methods, sets, generators, sockets, arrays, and many other types also work. Built-in lists and dictionaries do not support them directly.

import weakref

class MyList(list):
    pass

values = MyList([1, 2, 3])
reference = weakref.ref(values)
print(reference())

Subclasses of list and dict can gain support, but some types such as tuple and int still do not support weak references even when subclassed.

weakref and __slots__

Declaring __slots__ disables automatic weak-reference support unless the slot sequence includes "__weakref__".

import weakref

class User:
    __slots__ = ("name", "__weakref__")

    def __init__(self, name):
        self.name = name

user = User("Ana")
reference = weakref.ref(user)
print(reference().name)

This matters in memory-sensitive classes that use slots while also participating in weak caches or registries.

Garbage collection and testing

In CPython, non-cyclic objects are often destroyed immediately when the final strong reference disappears. Other implementations may collect later. The standard gc module documentation explains collection controls and debugging support.

import gc
import weakref

class Item:
    pass

item = Item()
reference = weakref.ref(item)
del item
gc.collect()
assert reference() is None

Use gc.collect() in controlled tests and investigations, not as a routine workaround for unclear object ownership or memory retention.

Weak references and cycles

Python’s cyclic collector already handles many reference cycles. Weak references help when a relationship does not conceptually represent ownership. For example, a child may weakly refer to a parent when the parent already strongly owns the child. That design clarifies which object controls lifetime.

Do not replace every relationship with weak references. At least one component must own an object for as long as it is needed. Otherwise objects may disappear prematurely and produce intermittent failures.

Threading considerations

A weak reference can become dead at any time after the last strong reference is removed. The safe pattern is to call the reference once and use the returned local object. Checking liveness with one call and retrieving with another creates a race.

Weak containers also do not automatically make compound operations atomic. If multiple threads update a registry or cache, use an appropriate lock around the complete read-modify-write sequence.

Common mistakes

  • Using a weak cache as persistent storage.
  • Keeping only weak references while expecting objects to remain alive.
  • Calling a reference twice in concurrent code.
  • Capturing the referent inside its callback or finalizer.
  • Passing the object’s own bound method to finalize().
  • Forgetting "__weakref__" in a slotted class.
  • Expecting direct support from lists, dictionaries, integers, or tuples.
  • Ignoring custom equality in WeakKeyDictionary.
  • Assuming immediate collection on every Python implementation.
  • Use weak references only for relationships that do not represent ownership.
  • Prefer weak containers and finalize() over low-level callbacks.
  • Dereference once before using an object.
  • Keep cleanup functions external and independent of the monitored instance.
  • Document that weak-cache entries may disappear.
  • Test behavior after strong references are removed.
  • Measure memory before and after adopting weak references.
  • Add size limits, expiration, and synchronization when the use case requires them.

Conclusion

Python weakref lets applications observe, index, and reuse objects without taking responsibility for keeping them alive. Raw references provide low-level control, while WeakValueDictionary, WeakKeyDictionary, and WeakSet solve common cache, metadata, and observer problems. WeakMethod supports bound callbacks, and finalize() provides one-time cleanup.

The module works best when ownership is explicit. Strong references should keep objects alive for real business needs; weak references should provide only auxiliary access. With cycle-free callbacks, correct slot support, concurrency-aware dereferencing, and lifecycle tests, weak references can reduce accidental memory retention without making behavior unpredictable.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    ZIP archive icon for a Python zipfile article
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipfile: Safe ZIP Archives

    Learn how to create, read, validate, and extract ZIP archives with Python zipfile in a predictable and secure workflow.

    Ler mais

    Tempo de leitura: 4 minutos
    27/07/2026
    Software dependency graph and Python task workflow
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python graphlib: Topological Sorting

    Learn Python graphlib to order dependencies, detect cycles, and coordinate independent tasks safely in parallel.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026
    Python code for safe context in asynchronous applications
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python contextvars for Safe Context

    Learn how Python contextvars isolates request data across asyncio tasks, logs, threads, and tests without unsafe global variables.

    Ler mais

    Tempo de leitura: 7 minutos
    26/07/2026
    Python code using cached_property to store expensive calculations
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python cached_property: Cache Object Values

    Learn Python cached_property to store expensive calculations, invalidate values, and prevent stale object caches.

    Ler mais

    Tempo de leitura: 6 minutos
    25/07/2026
    Python code with type-based function dispatch
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python singledispatch: Type-Based Functions

    Learn Python singledispatch to build type-based functions, reduce isinstance chains, and organize extensible polymorphism with clear examples.

    Ler mais

    Tempo de leitura: 6 minutos
    25/07/2026
    Python code demonstrating descriptors and attributes
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python Descriptors: Practical Guide

    Learn Python descriptors with __get__, __set__, validation, property, instance storage, testing, inheritance and practical design tips.

    Ler mais

    Tempo de leitura: 6 minutos
    22/07/2026