On Aug 13, 2014 7:04 PM, "Akira Li" <4kir4.1i@gmail.com> wrote:
Nick Coghlan <ncoghlan@gmail.com> writes:
On 12 August 2014 22:15, Steven D'Aprano <steve@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)): ...
Just a thought, would it bit wierd that: with (a as b, c as d): "works" with (a, c): "boom" with(a as b, c): ?
-- 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/yoavglazner%40gmail.com