Nick Coghlan <ncogh...@gmail.com> writes: > On 12 August 2014 22:15, Steven D'Aprano <st...@pearwood.info> wrote: >> Compare the natural way of writing this: >> >> with open("spam") as spam, open("eggs", "w") as eggs, frobulate("cheese") as >> cheese: >> # do stuff with spam, eggs, cheese >> >> versus the dynamic way: >> >> with ExitStack() as stack: >> spam, eggs = [stack.enter_context(open(fname), mode) for fname, mode in >> zip(("spam", "eggs"), ("r", "w")] >> cheese = stack.enter_context(frobulate("cheese")) >> # do stuff with spam, eggs, cheese > > You wouldn't necessarily switch at three. At only three, you have lots > of options, including multiple nested with statements: > > with open("spam") as spam: > with open("eggs", "w") as eggs: > with frobulate("cheese") as cheese: > # do stuff with spam, eggs, cheese > > The "multiple context managers in one with statement" form is there > *solely* to save indentation levels, and overuse can often be a sign > that you may have a custom context manager trying to get out: > > @contextlib.contextmanager > def dish(spam_file, egg_file, topping): > with open(spam_file), open(egg_file, 'w'), frobulate(topping): > yield > > with dish("spam", "eggs", "cheese") as spam, eggs, cheese: > # do stuff with spam, eggs & cheese > > ExitStack is mostly useful as a tool for writing flexible custom > context managers, and for dealing with context managers in cases where > lexical scoping doesn't necessarily work, rather than being something > you'd regularly use for inline code. > > "Why do I have so many contexts open at once in this function?" is a > question developers should ask themselves in the same way its worth > asking "why do I have so many local variables in this function?"
Multiline with-statement can be useful even with *two* context managers. Two is not many. Saving indentations levels along is a worthy goal. It can affect readability and the perceived complexity of the code. Here's how I'd like the code to look like: with (open('input filename') as input_file, open('output filename', 'w') as output_file): # code with list comprehensions to transform input file into output file Even one additional unnecessary indentation level may force to split list comprehensions into several lines (less readable) and/or use shorter names (less readable). Or it may force to move the inline code into a separate named function prematurely, solely to preserve the indentation level (also may be less readable) i.e., with ... as input_file: with ... as output_file: ... #XXX indentation level is lost for no reason with ... as infile, ... as outfile: #XXX shorter names ... with ... as input_file: with ... as output_file: transform(input_file, output_file) #XXX unnecessary function And (nested() can be implemented using ExitStack): with nested(open(..), open(..)) as (input_file, output_file): ... #XXX less readable Here's an example where nested() won't help: def get_integers(filename): with (open(filename, 'rb', 0) as file, mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as mmapped_file): for match in re.finditer(br'\d+', mmapped_file): yield int(match.group()) Here's another: with (open('log'+'some expression that generates filename', 'a') as logfile, redirect_stdout(logfile)): ... -- Akira _______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com