contextlib: closing() Masks the Real Error If close() Is Missing (tested)

contextlib.closing(thing) is a one-liner for "call thing.close() when this with block ends, even on an exception." It never checks that thing actually has a close() method when you construct it -- only when the block exits. We measured what happens to an exception raised inside the block when that check then fails.

1. An object without close() breaks even on a clean exit

import contextlib

class NoClose:
    pass

try:
    with contextlib.closing(NoClose()):
        print("inside, no exception")
except Exception as e:
    print(type(e).__name__, e)
inside, no exception
AttributeError: 'NoClose' object has no attribute 'close'

Measured: contextlib.closing() does no validation up front -- closing(NoClose()) succeeds, the body runs fine, and only on __exit__ does it call .close() and discover the object doesn't have one. That's expected enough on its own: a missing method fails when called, same as anywhere else in Python.

2. But if the block itself raised a real exception, that exception vanishes

import contextlib

class NoClose:
    pass

try:
    with contextlib.closing(NoClose()):
        raise ValueError("the real bug")
except Exception as e:
    print("raised:", type(e).__name__, e)
    print("the real bug is now just __context__:", e.__context__)
raised: AttributeError 'NoClose' object has no attribute 'close'
the real bug is now just __context__: the real bug

Measured: the exception that actually propagates out of the with statement is AttributeError: 'NoClose' object has no attribute 'close' -- not the ValueError("the real bug") that was actually raised inside the block. closing()'s __exit__ always calls .close() unconditionally, even while an exception is already propagating, and that call's own failure becomes the new, visible exception. The original ValueError survives only as __context__ -- present in a full traceback, but gone from str(e), type(e), and anything that only logs the top-level exception.

3. This only happens because NoClose has zero close() -- a working close() doesn't interfere

import contextlib

class HasClose:
    def close(self):
        pass

try:
    with contextlib.closing(HasClose()):
        raise ValueError("the real bug")
except Exception as e:
    print("raised:", type(e).__name__, e)
raised: ValueError the real bug

Measured: with a working close(), the original ValueError propagates normally -- the masking only happens when close() itself errors (missing method, or a close() that raises for its own reasons) while another exception is already in flight. The risk is specific to objects that only sometimes have a working close() -- for example a factory that can return either a real resource or a plain placeholder/mock without one.

If you want the original exception to win, check up front

import contextlib

def safe_closing(thing):
    if not hasattr(thing, "close"):
        raise TypeError(f"{type(thing).__name__} has no close() method")
    return contextlib.closing(thing)

This moves the failure to construction time, before anything inside the with block has a chance to run -- so a missing close() is its own clear error instead of silently replacing whatever the block itself was about to raise.

Frequently asked questions

Does contextlib.closing() check for a close() method when you create it?

No -- we measured closing(obj) succeeding at construction even when obj has no close() method at all; the AttributeError only happens later, when the with block exits and __exit__ tries to call .close().

What happens if the with block raises an exception and close() is missing?

The AttributeError from the missing close() becomes the exception that actually propagates, not the original one -- we measured the real exception surviving only as __context__, which most code that logs type(e) or str(e) never sees.

Does a working close() method avoid this problem?

Yes -- we measured that when close() succeeds normally, the original exception raised inside the with block propagates unchanged. The masking is specific to close() itself failing (missing method, or an exception inside close()) while another exception is already in flight.

How do I avoid the real error getting masked?

Validate that the object has a working close() before wrapping it in contextlib.closing(), so a missing method fails loudly at construction time instead of silently overwriting whatever exception the with block was about to raise.

Written with AI assistance. Measured on 2026-10-08 with Python 3.13.5's built-in contextlib module: raised an exception inside a contextlib.closing() block wrapping an object with no close() method, and checked which exception actually propagates. PlainHeaders is our extension; Python's contextlib module is built in.