Многострочный Python с оператором

Как правильно создать многострочный with в python? Я хочу открыть несколько файлов внутри одного with, но это достаточно далеко вправо, поэтому я хочу, чтобы это было на нескольких строках. Как это:

class Dummy:
    def __enter__(self): pass
    def __exit__(self, type, value, traceback): pass

with Dummy() as a, Dummy() as b,
     Dummy() as c:
    pass

К сожалению, это SyntaxError. Итак, я попробовал это:

with (Dummy() as a, Dummy() as b,
      Dummy() as c):
    pass

Тоже синтаксическая ошибка. Однако это сработало:

with Dummy() as a, Dummy() as b,\
     Dummy() as c:
    pass

Но что, если я хочу оставить комментарий? Это не работает:

with Dummy() as a, Dummy() as b,\
     # my comment explaining why I wanted Dummy() as c\
     Dummy() as c:
    pass

Также нет никаких очевидных изменений в размещении \s.

Есть ли чистый способ создать многострочный оператор with, который позволяет комментировать внутри него?


person Justin    schedule 24.06.2015    source источник
comment
На самом деле, большой вопрос заключается в том, что говорит об этом PEP-8, поскольку PEP-8 ограничивает длину строки 80 символами, что делает это необходимым.   -  person Justin    schedule 25.06.2015
comment
Мнение: PEP-8 великолепен, но я думаю, что ограничение в 80 символов слишком мало. Он основан на ограничениях старого терминала, а не на удобстве использования человеком. Я не защищаю строки с 300+ символами, но у меня нет проблем со 120-130 или около того.   -  person TigerhawkT3    schedule 25.06.2015
comment
@TigerhawkT3 TigerhawkT3 Я думаю, что ограничение в 80 символов тоже мало, но я вижу в этом преимущество, когда работаю над проектом, который требует, чтобы я одновременно открывал 5 файлов. Гораздо проще иметь возможность видеть каждый файл. Я мог бы сделать исключение для этого файла.   -  person Justin    schedule 25.06.2015
comment
PEP-8 явно разрешен с `\ ` продолжение строки для многострочных операторов with, так как вы не можете использовать неявное продолжение. Однако это не очень поможет в вашей ситуации, если вы хотите встроить комментарии.   -  person Eric Appelt    schedule 25.06.2015


Ответы (6)


Только Python 3.9+:

with (
    Dummy() as a,
    Dummy() as b,
    # my comment explaining why I wanted Dummy() as c
    Dummy() as c,
):
    pass

Python ≤ 3,8:

with \
    Dummy() as a, \
    Dummy() as b, \
    Dummy() as c:
    pass

К сожалению, комментарии с этим синтаксисом невозможны.

person Neil G    schedule 11.06.2020
comment
@Justin Это работает в версии 3.90a6, которую вы можете установить с помощью pyenv :) - person Neil G; 20.06.2020
comment
@Justin 3.9 выпущен - person Thomas Grainger; 09.11.2020
comment
@ThomasGrainger Спасибо за пинг. Я попробовал это, и это действительно работает с Python 3.9. - person Justin; 10.11.2020
comment
Это пока не официально поддерживается. Скорее всего, это станет официальной функцией в версии 3.10, но в настоящее время это недокументированное отклонение от официальной грамматики. Это может не работать в других реализациях Python 3.9, когда PyPy или какой-либо другой проект получает поддержку 3.9. - person user2357112 supports Monica; 10.11.2020
comment
@user2357112supportsMonica, разве официальная грамматика не та, что в cpython? Если нет, то где официальная грамматика? - person Neil G; 10.11.2020
comment
В официальных документах для оператора with скобки запрещены. Похоже, когда они создали полную спецификацию грамматики в документации, они создали это из файла грамматики, который имеет правило со скобками и оставил это правило, так что я предполагаю, что это изменение сейчас находится в полузадокументированном подвешенном состоянии. - person user2357112 supports Monica; 10.11.2020
comment
@ user2357112supportsMonica Достаточно честно, это должно быть PR в cpython. Гвидо очень гордится новой грамматикой PEG. Он быстро показал мне, что оператор with в скобках теперь работает. - person Neil G; 10.11.2020

Учитывая, что вы пометили этот Python 3, если вам нужно чередовать комментарии с вашими контекстными менеджерами, я бы использовал файл contextlib.ExitStack:

from contextlib import ExitStack

with ExitStack() as stack:
    a = stack.enter_context(Dummy()) # Relevant comment
    b = stack.enter_context(Dummy()) # Comment about b
    c = stack.enter_context(Dummy()) # Further information

Это эквивалентно

with Dummy() as a, Dummy() as b, Dummy() as c:

Преимущество этого заключается в том, что вы можете генерировать свои менеджеры контекста в цикле вместо того, чтобы перечислять каждый из них отдельно. В документации приведен пример, что если вы хотите открыть кучу файлов, и у вас есть имена файлов в списке, вы можете сделать

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]

Если ваши контекстные менеджеры занимают так много места на экране, что вы хотите поместить комментарии между ними, вероятно, у вас достаточно места, чтобы захотеть использовать своего рода цикл.


Как упоминает г-н Смертлесс в комментариях, в PyPI есть бэкпорт contextlib под именем contextlib2. Если вы используете Python 2, вы можете использовать реализацию backport ExitStack.


Кстати, причина, по которой вы не можете сделать что-то вроде

with (
        ThingA() as a,
        ThingB() as b):
    ...

это связано с тем, что ( также может быть первым токеном выражения для диспетчера контекста, и текущий синтаксический анализатор CPython не сможет сказать, какое правило он должен анализировать, когда увидит первый (. Это один из мотивирующих примеров для PEP 617, который вводит гораздо больше новый мощный синтаксический анализатор, так что желаемый синтаксис скоро может появиться.

person user2357112 supports Monica    schedule 25.06.2015
comment
Это отличное решение. - person Matthias; 25.06.2015
comment
На pypi есть бэкпорт улучшений contextlib для Python 2. Он обеспечивает ExitStack() среди прочего. - person Mr. Deathless; 25.11.2015

Это кажется мне самым аккуратным:

with open('firstfile', 'r') as (f1 # first
  ), open('secondfile', 'r') as (f2 # second
  ):
    pass
person TigerhawkT3    schedule 25.06.2015

Это не совсем чисто, но вы можете сделать это:

with Dummy() as a, Dummy() as b, (
     #my comment
     Dummy()) as c:
    pass

Синтаксических ошибок нет, но он не самый чистый. Вы также можете сделать это:

with Dummy() as a, Dummy() as b, Dummy(
     #my comment
     ) as c:
    pass

Попробуйте найти способ сделать это без использования комментариев в середине with.

person Justin    schedule 24.06.2015
comment
Молодец Квинканкс. Это довольно сумасшедший синтаксис, но я думаю, что это то, чего хотел ОП. Я не думал, что это возможно. - person DevShark; 25.06.2015
comment
Забавный факт: он является оператором. - person TigerhawkT3; 25.06.2015
comment
@DevShark Это делает то, что я хочу, но есть ли способ сделать это не таким сумасшедшим? Вот вопрос, который я хочу знать. Иногда мне хочется, чтобы Python мог сказать: «В конце этой строки есть оператор, который требует, чтобы что-то было после него, поэтому я лучше проверю следующую строку на наличие другого операнда». - person Justin; 25.06.2015
comment
Никаких обид; мое веселье возникло из-за того, что @DevShark не осознал. Но, если вам часто просто нужно дать еще несколько минут, чтобы разобраться в чем-то самостоятельно, может быть, вы могли бы сделать это, прежде чем задавать вопрос? Будьте более уверенными в себе. :) - person TigerhawkT3; 25.06.2015
comment
@TigerhawkT3 Я делаю это, прежде чем спрашивать. Я всегда провожу по крайней мере час, работая над проблемой, прежде чем задать вопрос. Только когда разочарование одолевает меня, я спрашиваю. И все же мне как-то обычно удается разобраться после того, как я спрошу. - person Justin; 25.06.2015
comment
@Downvoter Не могли бы вы объяснить, что плохого в этом ответе, чтобы я мог улучшить свои ответы в будущем? - person Justin; 25.06.2015
comment
@ Джастин, если ты обычно разбираешься во всем после того, как, наконец, сломался и задал вопрос, могу я предложить тебе резиновую утку? ;-) В любом случае, опубликовать вопрос в Интернете редко бывает плохо, если только вы вскоре сами на него ответите. Это хорошо для потомков и для пользы других! - person Victor Zamanian; 28.06.2018

Я бы сделал все простым и читабельным, добавив комментарий перед оператором with или в самой строке:

# my comment explaining why I wanted Dummy() as c
with Dummy() as a, Dummy() as b,\
     Dummy() as c: # or add the comment here
    pass
person MiniQuark    schedule 28.08.2019

Как ответ TigerhawkT3, но с отступом, который не вызывает ошибка pycodestyle E124:

with (
        open('firstfile', 'r')) as f1, (  # first
        open('secondfile', 'r')) as f2:  # second
    pass

IMO, это все еще уродливо, но, по крайней мере, оно проходит линтер.

person wjandrea    schedule 02.06.2019