@contextmanager
def multi(*cms):
if len(cms) > 0:
with cms[0]:
with multi(*cms[1:]):
yield
else:
yield
# use with 0..N items, it behaves the same
with multi(...):
pass
Prior to finding ExitStack, I've frequently seen people either give up and do something obviously wrong, or try to manually manage their own stack of __enter__ and __exit__ funcs (which works, but exception handling is non-obvious and often loses useful info). Personally I prefer the recursive route, since it very obviously behaves the same as if you wrote the nesting out by hand. It even retains accurate stack traces "naturally", unlike most non-ExitStack tactics I've seen through the years. ExitStack's stack-correcting tactic is... a bit more "interesting"[3], though AFAIK it achieves roughly the same stack trace in the end.Since ExitStack exists, and is likely more efficient (by how much, I have no idea), it's probably a better choice for most situations. And it's just broadly a bit nicer for things that yield resources.
It's always worth browsing stdlib documentation to find gems like this, there are quite a few neat things in most languages that seem fairly unknown.
[1]: https://docs.python.org/3/library/contextlib.html#supporting...
[2]: https://repl.it/@Groxx/TepidRosybrownLocatorprogram#main.py
[3]: https://github.com/python/cpython/blob/master/Lib/contextlib...