AI Skill Report Card
Using Weakref
Quick Start14 / 15
Pythonimport weakref class Node: pass obj = Node() r = weakref.ref(obj) # weak reference, doesn't keep obj alive print(r()) # -> <Node object> (call it to get the referent) del obj print(r()) # -> None, object was garbage collected
Recommendation▾
Add a brief example showing WeakKeyDictionary usage explicitly since it's referenced in the decision table but not demonstrated
Workflow13 / 15
- Identify the memory issue: reference cycles, caches growing unbounded, or child objects keeping parents alive.
- Choose the right weakref tool (see decision table below).
- Use
.callbackfor cleanup notification if you need to know when the referent dies. - Test garbage collection behavior explicitly (
del obj; gc.collect()) to confirm no leaks.
Progress:
- Identify objects that should NOT be kept alive by references
- Pick appropriate weakref primitive (ref, proxy, WeakValueDictionary, WeakKeyDictionary, WeakSet)
- Handle the case where the referent has been collected (check for
NoneorReferenceError) - Add callbacks if cleanup/logging is needed
- Verify with
gcmodule or memory profiling
Recommendation▾
Show a bad/incorrect example (e.g., using __del__ improperly) alongside the good pattern to reinforce contrast
Decision Table
| Need | Use |
|---|---|
| Single weak reference, explicit call to dereference | weakref.ref(obj, callback=None) |
| Weak reference that behaves like the object itself | weakref.proxy(obj, callback=None) |
| Dict mapping keys -> weakly-referenced values (cache pattern) | weakref.WeakValueDictionary() |
| Dict mapping weakly-referenced keys -> values (annotate objects w/o owning them) | weakref.WeakKeyDictionary() |
| Set of objects without keeping them alive | weakref.WeakSet() |
| Weak reference to a bound method (doesn't keep instance alive) | weakref.WeakMethod() |
Finalization/cleanup callback tied to object lifetime, safer than __del__ | weakref.finalize(obj, func, *args, **kwargs) |
Examples18 / 20
Example 1: Caching expensive objects without leaking memory
Input: Cache computed objects by key, but let them be garbage collected when nothing else references them.
Output:
Pythonimport weakref class ExpensiveObject: def __init__(self, value): self.value = value cache = weakref.WeakValueDictionary() def get_object(key): obj = cache.get(key) if obj is None: obj = ExpensiveObject(key) cache[key] = obj return obj a = get_object("x") print(len(cache)) # 1 del a import gc; gc.collect() print(len(cache)) # 0, auto-removed
Example 2: Observer pattern without preventing listener cleanup
Input: A subject notifies observers, but shouldn't keep dead observers alive.
Output:
Pythonimport weakref class Subject: def __init__(self): self._observers = weakref.WeakSet() def register(self, observer): self._observers.add(observer) def notify(self, *args): for obs in self._observers: obs.update(*args)
Example 3: Using finalize for cleanup instead of __del__
Input: Run cleanup code when an object is garbage collected, safely (no __del__ pitfalls).
Output:
Pythonimport weakref class Resource: def __init__(self, name): self.name = name self._finalizer = weakref.finalize(self, print, f"Cleaning up {name}") def close(self): self._finalizer() r = Resource("db-connection") del r # prints "Cleaning up db-connection"
Example 4: Weak reference to a bound method (avoid keeping instance alive via callback)
Input: Register a callback that is a bound method without preventing the instance from being collected.
Output:
Pythonimport weakref class Handler: def on_event(self): print("event handled") h = Handler() weak_method = weakref.WeakMethod(h.on_event) callback = weak_method() if callback is not None: callback() del h print(weak_method()) # None
Recommendation▾
Include a note on thread-safety considerations when using weakref collections in concurrent contexts
Best Practices
- Not all objects support weak references: built-ins like
int,list,dict,tuple(without__slots__workaround) can't be weakly referenced directly. Custom classes support it by default unless__slots__excludes__weakref__. - Always check if
ref()orproxy()returnedNone/raisedReferenceErrorbefore using the result — the referent may already be gone. - Prefer
weakref.finalizeover__del__for cleanup logic — it's more predictable, supports multiple finalizers, and doesn't resurrect objects. - Use
WeakValueDictionary/WeakKeyDictionaryfor caches and registries where entries should disappear automatically once no other strong references exist. - Use
proxy()when you want transparent attribute access instead of callingref()(). - Keep callbacks passed to
ref()/proxy()simple; they run after the object is already gone (during GC), so the callback receives the dead weakref, not the object.
Common Pitfalls
- Storing the weakref in a way that outlives its usefulness: iterating over a
WeakValueDictionary/WeakSetwhile objects are being collected can raiseRuntimeError: dictionary changed size during iteration. Take a snapshot (list(...)) before iterating if mutation during iteration is possible. - Assuming a weakref keeps working forever: always re-check dereferenced weakrefs before use, especially across function boundaries or after
await/context switches. - Trying to weakly reference an object without
__weakref__slot: define__slots__ = ('__weakref__', ...)explicitly if using__slots__and weakref support is needed. - Using
__del__for cleanup instead offinalize:__del__can resurrect objects, interacts badly with reference cycles and exceptions during interpreter shutdown. - Forgetting that a callback fires with the dead reference, not the object: don't expect
objstate inside the callback; the object's state is already unreachable.