Weak References (weakref)
Reviewed & published by Brayan K
A weak reference points to an object without keeping it alive, so the garbage collector can reclaim that object as soon as every ordinary (strong) reference is gone.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
The weakref module is the key to leak-free caches, observer patterns, and back-pointers — places where you want to refer to something without owning it.
What You'll Learn in This Lesson
- Creating a weak reference with weakref.ref
- Strong vs weak references and the GC
- Self-clearing caches with WeakValueDictionary
- Attaching data via WeakKeyDictionary
- Running callbacks when an object is collected
- When weakref prevents memory leaks
1 weakref.ref: A Reference That Doesn't Hold On
weakref.ref(obj) returns a callable proxy: call it to get the object back, or None if the object has been collected. We use del plus gc.collect() to make the demo's collection happen deterministically:
import weakref
import gc
class Node:
def __init__(self, name):
self.name = name
obj = Node("alpha")
ref = weakref.ref(obj) # weak: does NOT keep obj alive
print("alive:", ref() is not None) # alive: True
print("name:", ref().name) # name: alpha
del obj # drop the only strong reference
gc.collect() # force collection so the demo is deterministic
print("after del:", ref() is not None) # after del: False
print("deref:", ref()) # deref: Noneimport weakref
import gc
class Session:
def __init__(self, user):
self.user = user
s = Session("ada")
ref = weakref.ref(s)
print("before: alive =", ref() is not None)
print("user:", ref().user)
del s
gc.collect()
print("after: alive =", ref() is not None)
# ✅ Expected output:
# before: alive = True
# user: ada
# after: alive = False2 WeakValueDictionary: Self-Clearing Caches
A WeakValueDictionary holds its values weakly, so an entry automatically disappears once the value object is no longer used anywhere else. This is the classic way to build a cache that never leaks memory:
import weakref
import gc
class Image:
def __init__(self, name):
self.name = name
cache = weakref.WeakValueDictionary()
img = Image("logo.png")
cache["logo"] = img # value stored weakly
print("in cache:", "logo" in cache) # in cache: True
print("name:", cache["logo"].name) # name: logo.png
del img # nobody else references the Image now
gc.collect()
print("after del:", "logo" in cache) # after del: False
print("cache size:", len(cache)) # cache size: 0Contrast this with a normal dict: it would hold a strong reference, keeping every Image alive forever and slowly leaking memory. The weak dict lets unused entries evaporate on their own.
import weakref
import gc
class User:
def __init__(self, uid):
self.uid = uid
registry = weakref.WeakValueDictionary()
a = User(1)
b = User(2)
registry[1] = a
registry[2] = b
print("size before:", len(registry))
del a # drop one strong reference
gc.collect()
print("size after:", len(registry))
print("user 2 still here:", registry[2].uid)
# ✅ Expected output:
# size before: 2
# size after: 1
# user 2 still here: 23 Callbacks on Finalization
Sometimes you want to react when an object is reclaimed — to log it or release a resource. Pass a callback to weakref.ref(obj, callback), or use the cleaner weakref.finalize(obj, func, *args):
import weakref
import gc
class Resource:
def __init__(self, name):
self.name = name
def on_dead(ref):
print("callback: object was collected")
obj = Resource("db-connection")
ref = weakref.ref(obj, on_dead) # callback fires at collection
print("before del")
del obj
gc.collect() # triggers the callback
print("after del")
# Output:
# before del
# callback: object was collected
# after delimport weakref
import gc
class Buffer:
def __init__(self, size):
self.size = size
buf = Buffer(1024)
# finalize registers a function to run when buf is collected
weakref.finalize(buf, print, "buffer freed")
print("working...")
del buf
gc.collect()
print("done")
# ✅ Expected output:
# working...
# buffer freed
# done🎯 YOUR TURN: Fill in the Blanks
Fill in each ___ so the object is collected after del and the weak reference reports it. (Hint: the constructor is weakref.ref, and you force collection with gc.collect().)
import weakref
import gc
class Widget:
def __init__(self, label):
self.label = label
w = Widget("button")
# Make a weak reference -> use weakref.___
ref = weakref.___(w)
print("alive now:", ref() is not None)
del w
# Force collection so this is deterministic -> use gc.___
gc.___()
print("alive after del:", ref() is not None)
# Expected output:
# alive now: True
# alive after del: FalseCommon Errors
❌ Using a dead weakref without checking for None
obj = ref()
print(obj.name) # AttributeError: 'NoneType' object has no attribute 'name'✅ Dereference once and guard the result
obj = ref()
if obj is not None:
print(obj.name)
else:
print("already collected")❌ Trying to weakly reference a plain int, list, or tuple
weakref.ref([1, 2, 3])
# TypeError: cannot create weak reference to 'list' object✅ Use instances of your own classes (they support weakrefs)
class Box: pass
b = Box()
r = weakref.ref(b)
print(r() is b) # True❌ Expecting a weakref alone to keep the object alive
# A weakref does NOT count -> the object can vanish immediately
ref = weakref.ref(SomeClass()) # the new object may already be dead!✅ Keep a strong reference for as long as you need the object
obj = SomeClass() # strong reference keeps it alive
ref = weakref.ref(obj) # weak reference observes it🎯 Mini Challenge: Auto-Pruning Cache
Build a tiny object cache with a WeakValueDictionary. Add three objects, drop the strong references to two of them, force collection, and show that only the still-referenced object remains.
import weakref
import gc
class Doc:
def __init__(self, title):
self.title = title
cache = weakref.WeakValueDictionary()
a = Doc("A")
b = Doc("B")
c = Doc("C")
cache["a"] = a
cache["b"] = b
cache["c"] = c
print("start size:", len(cache))
# Keep only 'b' alive; drop the others
del a
del c
gc.collect()
print("after prune:", len(cache))
print("remaining:", sorted(cache.keys()))
print("survivor:", cache["b"].title)
# ✅ Expected output:
# start size: 3
# after prune: 1
# remaining: ['b']
# survivor: B📚 Keep Learning
- weakref — Weak references (official docs)
- weakref.finalize — clean finalization callbacks
- gc — interact with the garbage collector
Frequently Asked Questions
Lesson complete — you manage memory like a pro!
You can create a weakref.ref that doesn't keep objects alive, build self-clearing caches with WeakValueDictionary, attach data via WeakKeyDictionary, and run cleanup callbacks with weakref.finalize — the tools that keep long-running programs leak-free.
Practice quiz
What is a weak reference in Python?
- A reference that keeps an object alive twice as long
- A read-only copy of an object
- A reference that points to an object without keeping it alive
- A reference that only works for integers
Answer: A reference that points to an object without keeping it alive. A weak reference refers to an object but does NOT count toward keeping it alive, so it can be collected once strong references are gone.
Calling a dead weakref, ref(), returns what once the object has been collected?
- None
- The last value
- An empty string
- It raises KeyError
Answer: None. weakref.ref(obj) is callable; it returns the object, or None if the object has already been collected.
Which container holds its VALUES weakly, making it ideal for a self-clearing cache?
- WeakKeyDictionary
- OrderedDict
- defaultdict
- WeakValueDictionary
Answer: WeakValueDictionary. A WeakValueDictionary drops an entry when its value object is collected — perfect for caches keyed by an id.
What does WeakKeyDictionary hold weakly?
- Its values
- Its keys, so entries vanish when the key object is collected
- Both keys and values strongly
- Nothing — it is a normal dict
Answer: Its keys, so entries vanish when the key object is collected. A WeakKeyDictionary holds KEYS weakly; entries disappear when the key is collected — handy for attaching data to objects you don't own.
Why does this code raise a TypeError: weakref.ref([1, 2, 3])?
- Plain lists (and ints, tuples) don't support weak references
- Lists are too large to reference
- You must import list first
- The list is empty
Answer: Plain lists (and ints, tuples) don't support weak references. Built-in types like list, int, and tuple cannot be weakly referenced; instances of your own classes can.
In the demos, why are 'del obj' and 'gc.collect()' used together?
- Because weakref requires gc to be imported
- To create the weak reference
- To make collection happen deterministically so the output is predictable
- To convert the object to a string
Answer: To make collection happen deterministically so the output is predictable. del drops the last strong reference and gc.collect() forces collection immediately, making the weakref go dead deterministically.
Which is the recommended way to run cleanup code when an object is collected?
- Override __del__ on the class
- weakref.finalize(obj, func, *args)
- A while loop polling the object
- atexit.register
Answer: weakref.finalize(obj, func, *args). weakref.finalize is predictable, runs at most once, and avoids the pitfalls of __del__ (like accidentally keeping the object alive).
Why use weak references in a cache instead of a normal dict?
- A normal dict is slower to read
- Weak references compress the data
- Normal dicts cannot store objects
- A normal dict holds strong references and would keep every object alive, leaking memory
Answer: A normal dict holds strong references and would keep every object alive, leaking memory. A normal dict's strong references pin every cached object in memory; a WeakValueDictionary lets unused entries evaporate automatically.
Before using the result of obj = ref(), what should you always do?
- Sort it
- Check whether it is None, since the object may have been collected
- Convert it to a string
- Call gc.collect() on it
Answer: Check whether it is None, since the object may have been collected. Between two lines the object could be collected, so guard the dereferenced result: 'if obj is not None: ...'.
A weakref alone, with no strong reference, will:
- Keep the object alive forever
- Raise an error on creation
- Not keep the object alive — it may be collected immediately
- Make a deep copy of the object
Answer: Not keep the object alive — it may be collected immediately. A weakref does not count toward keeping an object alive; without a strong reference the object can vanish right away.
Continue this course
- Previous: Introspection with inspect
- Next: Checkpoint: Stdlib Power Tools — Combine struct, statistics and textwrap in a build — then a quiz
- Quick reference: Python cheat sheet
Frequently asked questions
What is a weak reference in Python?
A weak reference points to an object without keeping it alive. Normally every reference is 'strong' — as long as one exists, the object stays in memory. A weakref.ref lets you refer to an object but does NOT count toward keeping it around, so the garbage collector can reclaim it the moment all the strong references are gone.
Why would I want a reference that doesn't keep the object alive?
To avoid memory leaks in caches, observer lists, and back-references. A cache that strongly held every object would prevent anything from ever being freed. With weak references the cache automatically drops entries once nothing else uses them, and parent/child back-pointers won't create cycles that pin memory.
What's the difference between WeakValueDictionary and WeakKeyDictionary?
Both are dict-like containers that don't keep their referents alive. A WeakValueDictionary holds its VALUES weakly — entries vanish when the value object is collected (great for caches keyed by an id). A WeakKeyDictionary holds its KEYS weakly — entries vanish when the key object is collected (great for attaching extra data to objects you don't own).
How do I run code when an object is about to be destroyed?
Pass a callback to weakref.ref(obj, callback), or use weakref.finalize(obj, func, *args). The callback or finalizer runs when the object is garbage-collected, which is handy for cleanup like closing files or releasing resources — a safer alternative to relying on __del__.