Для чего предназначен оператор python «with»?

419

withСегодня я впервые столкнулся с заявлением Python . Я слегка использовал Python в течение нескольких месяцев и даже не знал о его существовании! Учитывая его немного неясный статус, я подумал, что стоит спросить:

  1. Для чего withпредназначен оператор Python ?
  2. Для чего ты это используешь?
  3. Есть ли какие-то ошибки, о которых мне нужно знать, или общие анти-паттерны, связанные с их использованием? Какие-нибудь случаи, когда это лучше, try..finallyчем использовать with?
  4. Почему это не используется более широко?
  5. Какие классы стандартной библиотеки совместимы с ним?
fmark
источник
5
Просто для справки , вотwith в документации по Python 3.
Алексей
Исходя из фона Java, он помогает мне запомнить его как соответствующую «попытку с ресурсами» в Java, даже если это может быть не совсем правильно.
vefthym

Ответы:

399
  1. Я считаю, что на это уже отвечали другие пользователи до меня, поэтому я добавляю его только для полноты картины: это withутверждение упрощает обработку исключений, инкапсулируя общие задачи подготовки и очистки в так называемых контекстных менеджерах . Более подробную информацию можно найти в PEP 343 . Например, openоператор сам по себе является менеджером контекста, который позволяет открывать файл, сохранять его открытым, пока выполнение находится в контексте withоператора, в котором вы его использовали, и закрывать его, как только вы выходите из контекста, не важно, оставили ли вы его из-за исключения или во время регулярного потока управления. Таким образом, withоператор может использоваться способами, подобными шаблону RAII в C ++: некоторый ресурс получаетсяwithзаявление и выпущен, когда вы выходите из withконтекста.

  2. Вот некоторые примеры: открытие файлов с использованием with open(filename) as fp:, получение блокировок с использованием with lock:(где lockэто экземпляр threading.Lock). Вы также можете создавать свои собственные контекстные менеджеры, используя contextmanagerдекоратор из contextlib. Например, я часто использую это, когда мне нужно временно изменить текущий каталог, а затем вернуться туда, где я был:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory

    Вот еще один пример , который временно перенаправляет sys.stdin, sys.stdoutи sys.stderrв какой - то другой дескрипторе файла и восстанавливают их позже:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"

    И, наконец, еще один пример, который создает временную папку и очищает ее при выходе из контекста:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
Тамаш
источник
20
Спасибо за добавление сравнения в RAII. Как программист на C ++, он рассказал мне все, что мне нужно было знать.
Фред Томсен
Хорошо, так позвольте мне получить это ясно. Вы говорите, что withоператор предназначен для заполнения переменной данными до тех пор, пока не будут выполнены все инструкции под ней, а затем освободить переменную?
Musixauce3000
Потому что я использовал его, чтобы открыть скрипт py. with open('myScript.py', 'r') as f: pass, Я ожидал , чтобы быть в состоянии назвать переменную , fчтобы увидеть содержание текста документа, так как это то , что будет отображаться , если документ был назначен fчерез обычный openзаявление: f = open('myScript.py').read(). Но вместо этого я получил следующее: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Что это значит?
Musixauce3000
3
@ Musixauce3000 - использование withне устраняет необходимость readв реальном файле. На withзвонки open- он не знает , что вам нужно сделать с ним - вы можете сделать искать, например.
Тони Саффолк 66
@ Musixauce3000 withОператор может заполнять переменную данными или вносить какие-либо другие изменения в среду до тех пор, пока не будут выполнены соответствующие инструкции, а затем выполнить любую необходимую очистку. Виды очистки, которые могут быть выполнены, такие как закрытие открытого файла, или, как это делает @Tamas в этом примере, изменение каталогов туда, где вы были раньше, и т. Д. Поскольку в Python есть сборка мусора, освобождение переменной не является важным вариант использования. withобычно используется для других видов очистки.
Боб
89

Я бы предложил две интересные лекции:

1. Оператор withиспользуется, чтобы обернуть выполнение блока методами, определенными менеджером контекста. Это позволяет try...except...finallyинкапсулировать общие шаблоны использования для удобного повторного использования.

2. Вы можете сделать что-то вроде:

with open("foo.txt") as foo_file:
    data = foo_file.read()

ИЛИ

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

ИЛИ (Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

ИЛИ

lock = threading.Lock()
with lock:
    # Critical section of code

3. Я не вижу здесь никакого Антипаттерна.
Цитируем погружение в Python :

попробуй .. в конце концов это хорошо. с лучше.

4. Я думаю, это связано с привычкой программистов использовать try..catch..finallyоператоры из других языков.

systempuntoout
источник
4
Это действительно вступает в свои права, когда вы имеете дело с потоками объектов синхронизации. Относительно редко встречается в Python, но когда они вам нужны, они вам действительно нужны with.
детски
1
diveintopython.org не работает (постоянно?). Отражение
прижимается
Пример хорошего ответа: открытый файл - это яркий пример, который показывает, что за кулисами открытия, io, закрытия файлов, операции полностью скрыты с пользовательским ссылочным именем
Angry 84
40

Оператор Python withявляется встроенной языковой поддержкой Resource Acquisition Is Initializationидиомы, обычно используемой в C ++. Он предназначен для безопасного сбора и выпуска ресурсов операционной системы.

withОператор создает ресурсы в пределах области / блока. Вы пишете свой код, используя ресурсы внутри блока. Когда блок выходит из системы, ресурсы освобождаются полностью, независимо от результата кода в блоке (то есть, нормально ли выходит блок или из-за исключения).

Многие ресурсы в библиотеке Python, которые подчиняются протоколу, требуемому withоператором, и поэтому могут использоваться с ним «из коробки». Однако любой может создать ресурсы, которые можно использовать в операторе with, с помощью хорошо документированного протокола: PEP 0343

Используйте его всякий раз, когда вы получаете ресурсы в вашем приложении, которые должны быть явно освобождены, такие как файлы, сетевые подключения, блокировки и тому подобное.

Тендай Мавуше
источник
27

Опять же, для полноты, я добавлю мой самый полезный вариант использования для withзаявлений.

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

Я устанавливаю точность по умолчанию для небольшого числа, а затем использую, withчтобы получить более точный ответ для некоторых разделов:

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

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

JudoWill
источник
26

Примером антипаттерна может быть использование withвнутри цикла, когда было бы более эффективно иметьwith внешний цикл

например

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

против

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

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

Джон Ла Рой
источник
10

См. PEP 343. - Оператор «with» , в конце приведен пример раздела.

... новая инструкция "with" для языка Python, чтобы можно было исключить стандартное использование операторов try / finally.

stefanB
источник
5

пункты 1, 2 и 3 достаточно хорошо освещены:

4: он относительно новый, доступен только в python2.6 + (или используя python2.5 from __future__ import with_statement)

cobbal
источник
4

Оператор with работает с так называемыми контекстными менеджерами:

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

Идея состоит в том, чтобы упростить обработку исключений, выполнив необходимую очистку после выхода из блока with. Некоторые из встроенных в Python уже работают как контекстные менеджеры.

zefciu
источник
3

Другим примером встроенной поддержки, который поначалу может показаться немного странным, когда вы привыкли к встроенному open()поведению, являются connectionобъекты популярных модулей базы данных, таких как:

Эти connectionобъекты являются менеджерами контекста и как таковые могут быть использованы вне коробки в with-statement, однако при использовании указанной выше сведению , что:

По with-blockокончании, с исключением или без, соединение не закрывается . Если with-blockзавершается с исключением, транзакция откатывается, в противном случае транзакция фиксируется.

Это означает, что программист должен сам позаботиться о том, чтобы закрыть соединение, но позволяет установить соединение и использовать его несколько раз with-statements, как показано в документах psycopg2 :

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

В приведенном выше примере вы заметите, что cursorобъекты psycopg2также являются контекстными менеджерами. Из соответствующей документации по поведению:

Когда cursorвыход выходит из with-blockнего, он закрывается, освобождая любой ресурс, в конечном итоге связанный с ним. На состояние транзакции это не влияет.

bgse
источник
3

В Python обычно оператор « with » используется для открытия файла, обработки данных, присутствующих в файле, а также для закрытия файла без вызова метода close (). Оператор with упрощает обработку исключений, предоставляя операции очистки.

Общая форма с:

with open(“file name”, mode”) as file-var:
    processing statements

примечание: нет необходимости закрывать файл, вызывая close () для file-var.close ()

Tushar.PUCSD
источник