What does weakref.finalize(obj, func) register, and when does func run?
answer
- Cleanup without extending a lifetime
- Registered against an object, not a block
- Fires once, at collection or exit
- Holds a weak reference to the referent
- The callback never sees the object
basics
~20 sweakref.finalize registers func as a cleanup callback for obj and returns a finalizer object. Python calls func exactly once: when obj is garbage collected, or at interpreter exit if obj is still alive. Registering does not keep obj alive.
solid answer
~40 s`weakref.finalize(obj, func, *args, **kwargs)` attaches a cleanup callback to `obj` and returns a small finalizer object. Internally it holds a **weak** reference to `obj`, so registration never extends the object's lifetime; when `obj` is reclaimed the callback runs as `func(*args, **kwargs)` and never receives `obj` itself. It fires **at most once**, and any finalizer still alive when the interpreter shuts down is called then, in reverse order of creation. The `weakref` module keeps the finalizer registered internally, so you do not have to store the returned object anywhere for it to work; you keep it only if you want to check `.alive`, cancel it with `detach()`, or trigger it early by calling it. The referent must be weak-referenceable, so plain `list`, `dict`, `tuple` and `int` values cannot be finalized.
code
python · 19 linesimport gc
import weakref
class Connection:
def __init__(self, name):
self.name = name
def close(name):
print("closed", name)
conn = Connection("primary")
finalizer = weakref.finalize(conn, close, conn.name)
print(finalizer.alive)
del conn
gc.collect()
print(finalizer.alive)go deeper
Recall the shape: register a function against an object, and it runs once when that object is collected or at interpreter exit. Be clear that registering does not keep the object alive and the callback never receives it.
Explain the mechanics: an internal weak reference whose callback is the finalizer, a module-level registry that keeps it alive, at-most-once firing, and the return value you get from calling it yourself.
Show when you reach for it instead of a with block: objects whose lifetime you do not control, and cleanup that must not silently vanish. Be explicit that the exit pass is best-effort and not a durability guarantee.
Own the policy question: which resources may be released non-deterministically at all. Argue for explicit scoped ownership as the default and finalizers as a leak backstop, and name what your service must still clean up out-of-band after a hard kill.
## The signature `weakref.finalize(obj, func, /, *args, **kwargs)` is a class in the standard library's `weakref` module. Constructing an instance *registers* a cleanup action: "when `obj` dies, call `func(*args, **kwargs)`". The constructor returns a finalizer object, which is itself callable. `obj` and `func` are positional-only, so every remaining positional and keyword argument is forwarded to `func` — there is no way to pass options to the finalizer through the constructor. ## How it detects death Under the hood the finalizer creates a weak reference to `obj` whose callback is the finalizer itself. A weak reference is one that does **not** count toward keeping an object alive: the reference-counting collector ignores it, and when the last strong reference goes away the object is destroyed and the weak reference's callback fires. That is the whole guarantee — `weakref.finalize` observes the object's death without participating in its lifetime. Crucially, the callback never receives `obj`. Your `func` only sees the arguments you supplied at registration time. That is deliberate: by the time the callback runs, the object is being torn down, and handing it back would allow *resurrection*. ## Where the finalizer lives Live finalizers are kept in a private module-level registry, keyed by the finalizer object. Two consequences follow, and both surprise people: 1. **You do not have to keep the returned object.** `weakref.finalize(obj, func)` on a bare line still works. This is the opposite of `weakref.ref(obj, callback)`, where dropping the reference object means the callback never fires. 2. **It works for objects trapped in reference cycles.** The cyclic garbage collector skips callbacks for weak references that are themselves part of the garbage being collected, to avoid running arbitrary code on half-destroyed data. Because the finalizer is reachable from the module registry rather than from the doomed object, it is never part of that garbage, so its callback still runs. ## At most once, and what it returns A finalizer fires exactly once. Calling the finalizer object yourself runs the callback immediately and returns `func`'s return value; every later call returns `None` and does nothing. This is what makes it a convenient basis for an idempotent `close()`. ## The exit pass Every finalizer that is still alive when the interpreter shuts down is called during exit, in **reverse order of creation** — newest first, on the assumption that later objects may depend on earlier ones. The `weakref` module arranges this by registering one hook with the `atexit` machinery the first time any finalizer is created. Exceptions raised by an exit-time callback are reported through `sys.excepthook` and do not stop the remaining finalizers or change the exit status. This exit pass is a courtesy, not a contract. It does not run if the process is killed with SIGKILL, if it calls `os._exit`, or if the interpreter dies from a fatal error. Never rely on it for anything whose absence corrupts state. ## Requirements and limits The referent must support weak references. Instances of ordinary classes do; built-in `list`, `dict`, `tuple`, `str` and `int` objects do not, and neither does an instance of a class that defines `__slots__` without including `__weakref__`. Registering one raises `TypeError: cannot create weak reference to 'list' object`. ```python import gc, weakref class Session: pass s = Session() f = weakref.finalize(s, print, "session cleaned up") print(f.alive) # True del s gc.collect() print(f.alive) # False - the callback already ran ``` ## Where it fits A `with` block is still the right tool whenever the resource's lifetime matches a block of code: it is deterministic, it runs on the exception path, and a reader can see the scope. `weakref.finalize` is for the cases where that shape does not exist — an object handed out by a factory, a cache entry, a handle stored on a long-lived object — where the only honest answer to "when should this be released?" is "when nobody is using the object any more". Treat it as a backstop that turns a silent leak into an eventual cleanup, not as a destructor you can schedule.
- Do you have to store the object that weakref.finalize returns for the callback to run?No. The `weakref` module keeps live finalizers in an internal registry, so a bare `weakref.finalize(obj, func)` call still fires. You keep the returned object only when you want the handle: reading `.alive`, cancelling with `detach()`, opting out of the exit pass, or calling it to clean up early. This is the opposite of `weakref.ref(obj, callback)`, where letting the reference object be collected means the callback is silently lost.
- Which objects can you not register a finalizer for?Anything that is not weak-referenceable. Built-in `list`, `dict`, `tuple`, `str` and `int` objects raise `TypeError: cannot create weak reference to 'list' object`, and so does an instance of a class that declares `__slots__` without listing `__weakref__`. The usual workaround is to finalize a wrapper object that owns the value, rather than the value itself.
- Does the callback receive the object that was collected?Never. It is called as `func(*args, **kwargs)` using only what you passed at registration time. That is a design decision, not an omission: at callback time the referent is already being destroyed, and handing it back would let a finalizer resurrect a half-dead object. It also means any argument you pass must be enough to do the cleanup on its own — a file descriptor number, a path, a socket — not the owner.
It is a note left with the building manager rather than in the tenant's apartment: nothing about leaving the note keeps the tenant there, and the manager acts on it once, the moment the flat is vacated.
saying these in an interview costs you the question
- Says registering a finalizer keeps the object alive
- Thinks the callback is handed the collected object
- Expects the callback to run more than once
- Assumes it always runs, even on SIGKILL or os._exit
- Believes you must store the returned finalizer object
- Tries to finalize a plain list or dict