[Python-Dev] Multiline with statement line continuation
Akira Li
4kir4.1i at gmail.com
Wed Aug 13 17:47:18 CEST 2014
Nick Coghlan <ncoghlan at gmail.com> writes:
> On 12 August 2014 22:15, Steven D'Aprano <steve at 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
More information about the Python-Dev
mailing list