What does contextlib.closing() do for an object that has close() but no __enter__?
answer
- An adapter, not a resource
- Turns close() into with-support
- Six lines you can write from memory
- __enter__ returns the object unchanged
- __exit__ just calls obj.close()
basics
~10 scontextlib.closing(obj) wraps any object in a context manager: enter returns obj unchanged and exit calls obj.close(). It lets a close()-only object be used in a with statement instead of a hand-written try/finally.
solid answer
~40 s`contextlib.closing(obj)` is a tiny adapter that supplies the two methods `with` needs. Its `__enter__` returns the object you passed in — the original, not a wrapper — so `with closing(make_cursor()) as cur:` binds `cur` to that object. Its `__exit__` calls `cur.close()` and returns `None`, so cleanup happens on every exit path (normal fall-through, `return`, `break`, or an exception) and no exception is ever swallowed. It is the direct replacement for `x = make_cursor()` / `try: ... finally: x.close()`. Reach for it when a type exposes `close()` but never grew `__enter__`/`__exit__`. Do not reach for it when the object already is a context manager: `with open(path) as f` is correct, and wrapping a type in `closing` when its own `__exit__` does more than call `close()` quietly gives you less cleanup, not more.
code
python · 20 linesfrom contextlib import closing
class Cursor:
def __init__(self):
self.closed = False
def close(self):
self.closed = True
print("close() called")
c = Cursor()
try:
with closing(c) as cur:
print("same object:", cur is c)
raise ValueError("boom")
except ValueError:
print("exception still propagated")
print("closed:", c.closed)go deeper
Be ready to state the two methods it supplies and write the equivalent try/finally on a whiteboard. Knowing that with closing(x) as y gives you back x itself is the whole answer at this level.
Explain why __exit__ returning None matters, and name a case where wrapping is wrong because the object already implements the protocol and its own __exit__ does more than close.
Show the failure modes you have actually hit: a late AttributeError from a missing close(), a raising close() masking the body's exception, and generators whose finally blocks you made deterministic with it.
Frame it as an API-design signal: a type that only offers close() forces every caller to remember cleanup. Argue for implementing the protocol on types you own and reserving closing() for code you cannot change.
### The whole mechanism `contextlib.closing` is one of the smallest useful classes in the standard library. Stripped of docstrings, it is essentially this: ```python class closing: def __init__(self, thing): self.thing = thing def __enter__(self): return self.thing def __exit__(self, *exc_info): self.thing.close() ``` Three points fall out of those six lines, and an interviewer is usually checking that you can derive all three rather than recite them. **`__enter__` returns the original object.** It is not a proxy and not a copy, so attribute access, method calls and `is` comparisons all behave exactly as they would on the unwrapped object. `with closing(obj) as x:` guarantees `x is obj`. **`__exit__` returns `None`.** In the `with` protocol, a falsy return value from `__exit__` means "I did not handle this exception", so anything raised inside the block propagates after `close()` has run. `closing` cleans up; it never suppresses. That is precisely what makes it a faithful stand-in for `try`/`finally` rather than for `try`/`except`. **`__exit__` ignores the exception triple.** It does not care whether the block succeeded or blew up — `close()` is called either way, exactly once. ### Why it exists The `with` statement only accepts objects that implement the context-manager protocol. Plenty of objects — older library types, hand-rolled classes, generator objects, handles returned by network and database helpers — expose a `close()` method and nothing else, because they predate `with` or were never updated. Before `contextlib.closing`, using one safely meant writing the cleanup by hand: ```python thing = make_thing() try: use(thing) finally: thing.close() ``` `closing` compresses that to `with closing(make_thing()) as thing:` and, more importantly, makes the intent visible on the same line as the acquisition. The two forms are exactly equivalent, including a subtlety worth naming: the object is constructed *before* `closing` ever sees it, so if `make_thing()` itself raises, there is nothing to close and no cleanup runs — identical to the `try`/`finally` version, where the assignment also precedes the `try`. ### Generator objects: the case people forget Generator objects have a `close()` method and no `__enter__`, which makes them the classic target. Closing a suspended generator throws `GeneratorExit` in at the point of the `yield`, running its `finally` blocks immediately instead of whenever the garbage collector gets to it. If a generator holds a resource open across a `yield`, `with closing(gen) as g:` is how you make the release deterministic. ### When not to use it - **The object is already a context manager.** Files, sockets, locks and many others implement the protocol themselves. Wrapping them adds noise, and can actively lose behaviour: `subprocess.Popen.__exit__`, for example, closes the child's streams *and* waits for the process, while `closing` would only call `close()`. - **`close()` is not the right cleanup.** A handle that needs a commit-or-rollback decision, or cleanup that depends on whether the block raised, needs a real manager that inspects the exception arguments — not a blind `close()`. - **The number of things to clean up is decided at runtime.** One `closing` per object stops scaling once the count is dynamic; `contextlib.ExitStack` is the tool for that case. ### Sharp edges - **No up-front check.** `closing` does not verify that the object has a `close()` method when you construct it. If it does not, you get an `AttributeError` at the *end* of the block — after the body has already run and possibly after another exception is in flight. - **A raising `close()` masks the body's error.** If the block is unwinding on a `ValueError` and `close()` then raises, callers see the error from `close()`, with the original attached as its context. A `close()` that can plausibly fail deserves its own handling. - **One instance, one use.** The wrapper is cheap; make a new one per `with`. Reusing a single `closing` instance across two blocks closes the same object twice, and not every `close()` is idempotent. ### What to say in an interview "It adapts a `close()`-only object to the `with` protocol: `__enter__` gives me back the object, `__exit__` calls `close()`, and because `__exit__` returns `None` nothing is suppressed. It is `try`/`finally` with the cleanup written next to the acquisition — and I skip it entirely for objects that already support `with`."
- Does contextlib.closing suppress an exception raised inside the with body?No. Its `__exit__` calls `close()` and then returns `None`, and a falsy return from `__exit__` means the exception was not handled, so it propagates after cleanup. Only a manager whose `__exit__` returns a true value swallows one; `closing` never does. That is exactly why it maps onto `try`/`finally` and not onto `try`/`except`.
- What happens if close() itself raises inside a closing() block?The error from `close()` propagates out of the `with`. If the body was already unwinding on another exception, the original is attached as the new one's context and printed under "During handling of the above exception", but the error callers actually catch is the one from `close()`. A `close()` that can realistically fail is worth wrapping or logging.
- When would you not reach for closing()?When the object already implements `__enter__`/`__exit__` — files, sockets, locks — use it directly, since its own `__exit__` may do more than call `close()`. Also skip it when cleanup depends on whether the block raised, which needs a real manager, and when the number of objects to release is only known at runtime, which is what `contextlib.ExitStack` is for.
It is a travel plug adapter: it changes nothing about the appliance, it only lets it fit the socket the with statement provides.
saying these in an interview costs you the question
- Says closing() swallows exceptions raised in the block
- Wraps open() in closing() out of habit
- Thinks closing() binds a wrapper, not the original object
- Believes close() runs only when the block succeeds
- Confuses closing() with contextlib.suppress
- Assumes it works on an object with no close() method