Вопрос-Ответ

What is the python "with" statement designed for?

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

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


  1. Для чего предназначен оператор Python with?

  2. Для чего вы его используете?

  3. Есть ли какие-либо ошибки, о которых мне нужно знать, или общие антишаблоны, связанные с его использованием? Есть ли случаи, когда его лучше использовать try..finally чем with?

  4. Почему он не используется более широко?

  5. Какие стандартные библиотечные классы совместимы с ним?

Переведено автоматически
Ответ 1

  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

Ответ 2

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

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:


попробовать .. наконец-то хорошо. with лучше.


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

Ответ 3

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

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

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

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

Ответ 4

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

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

python