How do you cancel a weakref.finalize callback, run it early, or check if it ran?
answer
- The returned object is a control handle
- One way to cancel, one way to trigger
- Calling it gives idempotent cleanup
- detach unregisters and hands back the tuple
- alive is the closed flag you expose
basics
~20 sCall the finalizer object to run the cleanup now and mark it dead; call its detach() method to cancel it so it never runs; read its alive property to see whether it is still pending. peek() inspects the registration without cancelling it.
solid answer
~50 sThe object returned by `weakref.finalize` is a small handle with four operations. **Calling it** invokes the callback immediately, returns whatever the callback returned, and marks the finalizer dead — every later call returns `None`, which is exactly the idempotent `close()` you want. **`detach()`** cancels: it unregisters the finalizer and returns the `(obj, func, args, kwargs)` tuple, so the callback will never run, and you use it when ownership of the resource passes to someone else. **`.alive`** is a boolean property, true while the finalizer is still registered. **`peek()`** returns the same tuple as `detach()` but leaves the registration in place, for inspection. Both `detach()` and `peek()` return `None` if the referent is already gone. The usual pattern is to keep the finalizer as a private attribute and expose `close()` as calling it and `closed` as `not finalizer.alive`.
code
python · 23 linesimport shutil
import tempfile
import weakref
class Workspace:
def __init__(self):
self.dir = tempfile.mkdtemp()
self._finalizer = weakref.finalize(self, shutil.rmtree, self.dir, True)
def close(self):
self._finalizer()
@property
def closed(self):
return not self._finalizer.alive
w = Workspace()
print(w.closed)
w.close()
print(w.closed)
w.close()go deeper
Learn the four moves on the returned object: call it to clean up now, detach() to cancel, .alive to check whether it is still pending, peek() to look without changing anything.
Be able to build an idempotent close() on top of it and explain why a second call is a harmless no-op, and why detach() exists for the case where ownership moves elsewhere.
Show the full resource-class shape you would ship: private finalizer, close() and a closed property, a with-block interface, detach() on the ownership-transfer path, and a collect-and-assert test.
Decide the house pattern for releasable resources so every team writes the same one, and rule on which cleanups may be skipped at shutdown by clearing .atexit rather than running against a half-torn-down process.
## The handle you get back `weakref.finalize(...)` returns a finalizer object. You do not need it for the callback to fire — the module keeps its own registry — but keeping it gives you control over the registration, and that is what turns `weakref.finalize` from a passive safety net into the backbone of a well-behaved resource class. ## Calling it: run the cleanup now The finalizer is callable with no arguments. Calling it: 1. removes the registration, 2. invokes `func(*args, **kwargs)`, 3. returns whatever `func` returned. Every subsequent call is a no-op returning `None`. That combination is precisely the contract of a well-written `close()`: it does the work once, and calling it twice is harmless. You get idempotence for free rather than hand-rolling a `self._closed` flag that someone will eventually forget to set on an error path. ```python import shutil, tempfile, weakref class Workspace: def __init__(self): self.dir = tempfile.mkdtemp() self._finalizer = weakref.finalize(self, shutil.rmtree, self.dir, True) def close(self): self._finalizer() @property def closed(self): return not self._finalizer.alive ``` This class now cleans up three ways: explicitly via `close()`, implicitly when the last reference goes, and as a last resort at interpreter exit — and never twice. ## detach(): cancel it `detach()` unregisters the finalizer *without* running the callback and returns the `(obj, func, args, kwargs)` tuple that was registered. After it, `.alive` is false and nothing will ever fire. Two real uses: * **Ownership transfer.** Your object hands its file descriptor, buffer or temp directory to a caller who now owns closing it. Detaching prevents a double close. * **Reconfiguration.** You detach the current registration, take the tuple it returns, and register a new finalizer with different arguments. If the referent has already been collected, `detach()` returns `None` — there was nothing left to hand back. ## peek(): look without touching `peek()` returns the same `(obj, func, args, kwargs)` tuple but leaves the registration intact, so it is the read-only sibling of `detach()`. Note that it hands back a **strong** reference to the referent for as long as you hold the tuple, which is fine for a debug print and a trap if you stash it somewhere long-lived. It is mostly a diagnostic: "what cleanup is this object still going to do?" ## .alive: is it still pending `.alive` is a read-only boolean property: true while the finalizer is registered, false once it has fired, been called, or been detached. It is the natural implementation of a public `closed` flag, and it is what a test asserts on after dropping a reference and calling `gc.collect()` to prove that the object was actually collectible. ```pycon >>> import weakref >>> class R: pass ... >>> r = R() >>> f = weakref.finalize(r, print, "cleanup") >>> f.alive True >>> f() cleanup >>> f.alive False >>> f() is None True ``` ## The fourth knob: .atexit There is one more attribute, and unlike the others it is writable. `.atexit` defaults to true and controls whether a still-live finalizer is invoked during the interpreter's shutdown pass. Set `f.atexit = False` for cleanup that is meaningless or actively harmful during shutdown — flushing to a service you are about to disconnect from, writing a "clean shutdown" marker, touching a resource the OS reclaims anyway. It is not a constructor argument: `weakref.finalize(obj, func, atexit=False)` would forward `atexit=False` to `func` as a keyword argument, because everything after `func` belongs to the callback. Set it on the returned object. ## Putting it together A resource class that owns something releasable should keep its finalizer private, expose `close()` as calling it, expose a `closed` property from `.alive`, implement `__enter__`/`__exit__` so callers can scope it in a `with` block, and use `detach()` on the one path where ownership leaves. That covers deterministic release, safe double release, and an eventual backstop, with no bookkeeping flags of its own.
- What is the difference between calling the finalizer and calling its detach() method?Calling the finalizer runs the cleanup and returns the callback's result; `detach()` cancels the cleanup and returns the `(obj, func, args, kwargs)` tuple instead. Both leave `.alive` false, so afterwards nothing will fire again. Use the call when you are releasing the resource now, and `detach()` when someone else has taken over responsibility for releasing it and running the callback would be a double free.
- Why can't you pass atexit=False to weakref.finalize when you register it?Because `obj` and `func` are positional-only and everything after them is forwarded to the callback: `atexit=False` would arrive as a keyword argument to `func`, usually raising `TypeError`. `.atexit` is a writable property on the returned finalizer, so you set it afterwards: `f = weakref.finalize(obj, func); f.atexit = False`.
- peek() returns the referent - is holding that tuple dangerous?It can be. The tuple carries a **strong** reference to the object, so anything that keeps the tuple keeps the object alive and defers the very cleanup you were inspecting. It is fine inside a debug print or a short-lived expression; storing it on a long-lived structure recreates the classic pinning bug from the other direction.
saying these in an interview costs you the question
- Thinks detach() runs the callback before unregistering
- Expects a second call to re-run the cleanup
- Passes atexit=False as a constructor keyword argument
- Adds a separate closed flag instead of reading alive
- Believes the finalizer must be stored for the callback to fire
- Keeps the peek() tuple and wonders why nothing is collected