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

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

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

10 ответов


  1. я считаю, что на это уже ответили другие пользователи до меня, поэтому я добавляю его только для полноты:with оператор упрощает обработку исключений путем инкапсуляции общих задач подготовки и очистки в так называемом контекст-менеджеров. Более подробную информацию можно найти в PEP 343. Например,open оператор сам по себе является контекстным менеджером, который позволяет открывать файл, держать его открытым, пока выполняется контекст with оператор, где вы его использовали, и закройте его, как только вы покинете контекст, независимо от того, оставили ли вы его из-за исключения или во время регулярного потока управления. The with таким образом, оператор может использоваться способами, аналогичными RAII pattern в 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
    

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

  • PEP 343 "С" заявление
  • Effbot понимание Python "с" заявление

1. The 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 заявление с других языков.


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

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

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

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


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

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

vs

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

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


снова для полноты я добавлю свой самый полезный вариант использования для 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

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


посмотреть PEP 343-оператор 'with', в конце есть примерный раздел.

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


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

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

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


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

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


в python обычно"С" оператор используется для открытия файла, обработки данных, присутствующих в файле, а также для закрытия файла без вызова метода close (). оператор "with" упрощает обработку исключений, предоставляя действия по очистке.

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

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

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


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

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

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

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

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