Для чего предназначен оператор python "with"?
Сегодня я впервые столкнулся с оператором Python with
. Я несколько месяцев слегка пользовался Python и даже не знал о его существовании! Учитывая его несколько неясный статус, я подумал, что стоит спросить:
- Для чего предназначен оператор Python
with
? - Для чего вы его используете?
- Есть ли какие-либо ошибки, о которых мне нужно знать, или общие антишаблоны, связанные с его использованием? Есть ли случаи, когда его лучше использовать
try..finally
чемwith
? - Почему он не используется более широко?
- Какие стандартные библиотечные классы совместимы с ним?
Переведено автоматически
Ответ 1
Я полагаю, что на этот вопрос уже отвечали другие пользователи до меня, поэтому я добавляю его только для полноты картины:
with
оператор упрощает обработку исключений, инкапсулируя общие задачи подготовки и очистки в так называемые контекстные менеджеры. Более подробную информацию можно найти в PEP 343 . Например,open
инструкция сама по себе является контекстным менеджером, который позволяет вам открывать файл, сохранять его открытым до тех пор, пока выполнение выполняется в контекстеwith
инструкции, в которой вы его использовали, и закрывать его, как только вы выходите из контекста, независимо от того, вышли ли вы из-за исключения или во время обычного потока управления. Таким образом, операторwith
может использоваться способами, аналогичными шаблону RAII в C ++: некоторый ресурс приобретаетсяwith
оператором и освобождается, когда вы покидаетеwith
контекст.Вот несколько примеров: открытие файлов с помощью
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
Я бы предложил две интересные лекции:
- PEP 343 Инструкция "with"
- Эффективный бот, понимающий оператор Python "with"
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
Я часто использую это с гипергеометрическим тестом, который требует разделения больших чисел, образующих форм-факториалы. При вычислениях в геномном масштабе вы должны быть осторожны с ошибками округления и переполнения.