A finalizer can be called explicitly once; its callback must not retain the object it watches.
Make this comfortable
Python weakref.finalize: run an idempotent cleanup callback once
Operation contract
A receipt lease registers a callback with only the receipt ID, not a bound method on the lease. The owner calls the finalizer during normal shutdown, then calls it again. The callback records one release and the finalizer is no longer alive.
Failure boundary
This fixture calls cleanup explicitly, so it does not depend on garbage-collection timing. A finalizer is a fallback for abandoned ownership, not a substitute for with or try/finally around a real resource. Exceptions from callbacks triggered by collection cannot be returned to the abandoned caller.
Working program
import weakref
class ReceiptLease:
pass
released_receipts = []
lease = ReceiptLease()
cleanup = weakref.finalize(lease, released_receipts.append, "R-47")
print("registered", cleanup.alive)
cleanup()
cleanup()
print("released", released_receipts)
print("still_live", cleanup.alive)Output
registered True
released ['R-47']
still_live FalseCosts and limits
The finalizer retains its callback and arguments until it runs or is detached. Capturing a large graph there can prolong its lifetime; capturing the watched object can prevent automatic collection entirely.
Common Mistakes
- Do not pass a bound method of the watched object as its finalizer callback.
- Do not rely on collection timing for prompt resource release.
- An explicit second call does not repeat a live finalizer's cleanup.
Connected lessons
python
weakref-finalizer-once
