[Python-Dev] Multiline with statement line continuation

Ian Cordasco graffatcolmingov at gmail.com
Tue Aug 12 15:04:35 CEST 2014

On Tue, Aug 12, 2014 at 7:15 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Tue, Aug 12, 2014 at 10:28:14AM +1000, Nick Coghlan wrote:
>> On 12 Aug 2014 09:09, "Allen Li" <cyberdupo56 at gmail.com> wrote:
>> >
>> > This is a problem I sometimes run into when working with a lot of files
>> > simultaneously, where I need three or more `with` statements:
>> >
>> >     with open('foo') as foo:
>> >         with open('bar') as bar:
>> >             with open('baz') as baz:
>> >                 pass
>> >
>> > Thankfully, support for multiple items was added in 3.1:
>> >
>> >     with open('foo') as foo, open('bar') as bar, open('baz') as baz:
>> >         pass
>> >
>> > However, this begs the need for a multiline form, especially when
>> > working with three or more items:
>> >
>> >     with open('foo') as foo, \
>> >          open('bar') as bar, \
>> >          open('baz') as baz, \
>> >          open('spam') as spam \
>> >          open('eggs') as eggs:
>> >         pass
>> I generally see this kind of construct as a sign that refactoring is
>> needed. For example, contextlib.ExitStack offers a number of ways to manage
>> multiple context managers dynamically rather than statically.
> I don't think that ExitStack is the right solution for when you have a
> small number of context managers known at edit-time. The extra effort of
> writing your code, and reading it, in a dynamic manner is not justified.
> 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
> I prefer the first, even with the long line.

I agree with Steven for *small* numbers of context managers. Once they
become too long though, either refactoring is severely needed or the
user should ExitStack.

To quote Ben Hoyt:

> Is it meaningful to use "with" with a tuple, though? Because a tuple
> isn't a context manager with __enter__ and __exit__ methods. For
> example:
> >>> with (1,2,3): pass
> ...
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> AttributeError: __exit__
> So -- although I'm not arguing for it here -- you'd be turning an code
> (a runtime AttributeError) into valid syntax.

I think by introducing parentheses we are going to risk seriously
confusing users who may then try to write an assignment like

a = (open('spam') as spam, open('eggs') as eggs)

Because it looks like a tuple but isn't and I think the extra
complexity this would add to the language would not be worth the
benefit. If we simply look at Ruby for what happens when you have an
overloaded syntax that means two different things, you can see why I'm
against modifying this syntax. In Ruby, parentheses for method calls
are optional and curly braces (i.e, {}) are used for blocks and hash
literals. With a method on class that takes a parameter and a block,
you get some confusing errors, take for example:

class Spam
  def eggs(ham)
    puts ham
    yield if block_present?

s = Spam.new
s.eggs {monty: 'python'}
SyntaxError: ...


s.eggs({monty: 'python'})

Will print out the hash. The interpreter isn't intelligent enough to
know if you're attempting to pass a hash as a parameter or a block to
be executed. This may seem like a stretch to apply to Python, but the
concept of muddling the meaning of something already very well defined
seems like a bad idea.

More information about the Python-Dev mailing list