6

I'm trying to understand what is new with the new parenthesized context managers feature in Python 3.10 (top item in new features here).

My test example was to try and write:

with (open('file1.txt', 'r') as fin, open('file2.txt', 'w') as fout):
    fout.write(fin.read())

A super simple test, and it works perfectly in Python 3.10.

My problem is that it also works perfectly in Python 3.9.4?

Testing this in Python 3.8.5, it looks like it doesn't work, raising the expected SyntaxError.

Am I misunderstanding this update as it seems this new syntax was introduced in 3.9?

martineau
  • 119,623
  • 25
  • 170
  • 301
James Briggs
  • 854
  • 6
  • 13
  • 8
    I believe the PEG parser that allows this was added to Python 3.9 alpha 6, as an implementation detail of CPython, rather than a guaranteed feature of Python itself. See the [migration plan for PEP-617](https://www.python.org/dev/peps/pep-0617/#migration-plan). (So strictly speaking, it's *not* valid Python 3.9, though CPython accepts it. PyPy, for example, could claim to support Python 3.9 without accepting the parenthesized context manager.) – chepner Jun 02 '21 at 16:36
  • 1
    (And yes, I know that PyPy only supports Python 3.7 at this time. I'm not really aware of any alternate implementations that are closer to matching CPython in terms of language versions supported.) – chepner Jun 02 '21 at 16:41
  • 1
    What is new about it is that previously you couldn't put one or more inside of them parentheses making it easy to spread multiple one over several lines — so what folks often did was end the lines with a backslash line-continuation character instead (which is/was generally considered ugly - see [PEP 8 - Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/#id19/)). – martineau Jun 02 '21 at 17:26
  • @chepner okay so the PEG parser that enabled the change meant that the new syntax was allowed, but not officially supported until 3.10? That's awesome and explains a lot, thank you! – James Briggs Jun 02 '21 at 20:58

1 Answers1

2

I didn't know this was in, but I'm glad to see it. Professionally I've used 3.6 (which doesn't have this), and with multiple long line context managers and black, it's really hard to format:

If you have this:

with some_really_long_context_manager(), and_something_else_makes_the_line_too_long():
    pass

You have to write this which is ugly:

with some_really_long_context_manager(), \
    and_something_else_makes_the_line_too_long():
    pass

If your arguments are too long:

with some_context(and_long_arguments), and_another(now_the_line_is_too_long):
    pass

You can do something like:

with some_context(and_long_arguments), and_another(
    now_the_line_is_too_long
):
    pass

But that doesn't work if one context manager doesn't take arguments, and it looks slightly odd anyway:

with some_context(and_long_arguments), one_without_arguments(
    ), and_another(now_the_line_is_too_long):
    pass

To do this, you have to rearrange:

with some_context(and_long_arguments), and_another(
    now_the_line_is_too_long
), one_without_arguments():
    pass

With the new syntax, this can be formatted as:

with (
    some_context(and_long_arguments),
    one_without_arguments(), 
    and_another(now_the_line_is_too_long), 
):
    pass

This also makes diffs more readable.

blueteeth
  • 3,330
  • 1
  • 13
  • 23