That's really complicated. The great thing about the "with" clause is that it Just Works. The easiest way to handle opens and closes is thus exception-safe.
What's wrong with indenting? You're entering a scope; indenting is appropriate.
I wrote "if that's an issue". For most cases it's not an issue.
The primary use case ExitStack is not to reduce indentation levels; it's to support "a variable number of context managers and other cleanup operations in a single with statement".
You wrote "The easiest way to handle opens and closes is thus exception-safe".
Consider if you need to merge up to 50 files containing sorted lines, where the result must also be sorted, and where you should not load all of the data into memory first. (Sorted according to UTF-8.) How would you do it? Not so easy, is it?
With ExitStack you could write it as something like the following sketch:
filenames = [ list, of, file, names]
with ExitStack() as stack:
files = [stack.enter_context(open(fname, "rb")) for fname in filenames]
with open("merged_lines.txt", "wb") as output:
output.writelines(heapq.merge(*files))
As you say, the great thing about this with clause is that it Just Works.
A side-effect of making ExitStack work is that it might help if the indentation depth is a problem, which mappu apparently has experienced.
What's wrong with indenting? You're entering a scope; indenting is appropriate.