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


419

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

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

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операторы из других языков.


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

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


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.


5

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

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


4

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

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

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


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него, он закрывается, освобождая любой ресурс, в конечном итоге связанный с ним. На состояние транзакции это не влияет.


3

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.