[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