Use closing for an iterator with a close method
Use closing for an iterator with a close method
How do you use closing for an iterator that has close() but is not a context manager? Wrap the iterator in with contextlib.closing(iterator) as values:. The first half of this fixture shows the failure: a break after values 1 and 2 leaves an ordinary ThreeValues object with closed=False. Breaking a for loop only stops iteration; it does not call an arbitrary iterator's close() method.
The corrected half wraps a new iterator with closing. It consumes the same [1, 2], breaks at the same point, and then leaves the with block. closing calls close() in its cleanup path, so the flag becomes True. The assertions distinguish the unowned iterator from the context-managed one and verify the exact consumption boundary.
contextlib.closing is useful for an object that exposes close() but does not implement the context-manager protocol. It is effectively a try/finally wrapper around that call. It does not make close() idempotent, drain the remaining iterator values, or guarantee that every arbitrary object has meaningful cleanup semantics. Prefer an object's native with support when it has it; the Python documentation notes that closing chiefly serves types that are not context managers. Python contextlib documentation
AI assistance disclosure: This article was drafted with AI assistance and checked against the cited documentation and a synthetic example.
from contextlib import closing
class ThreeValues:
def __init__(self):
self._values = iter((1, 2, 3))
self.closed = False
def __iter__(self):
return self
def __next__(self):
return next(self._values)
def close(self):
self.closed = True
def consume_two(values):
consumed = []
for value in values:
consumed.append(value)
if value == 2:
break
return consumed
unmanaged = ThreeValues()
assert consume_two(unmanaged) == [1, 2]
assert unmanaged.closed is False
managed = ThreeValues()
with closing(managed) as values:
consumed = consume_two(values)
assert consumed == [1, 2]
assert managed.closed is True
print(f"without_closing={unmanaged.closed}")
print(f"consumed={consumed}")
print(f"after_closing={managed.closed}")
without_closing=False
consumed=[1, 2]
after_closing=True