Batu Lab NotesPractical developer guides

Use closing for an iterator with a close method

By Batu ยท English technical notes

Also published in our primary archive.

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