Идея утверждения with
состоит в том, чтобы сделать «правильные поступки» путем наименьшего сопротивления. В то время как пример файла является самым простым, поточные блокировки фактически предоставляют более классический пример неочевидного глючного кода:
try:
lock.acquire()
# do stuff
finally:
lock.release()
Этот код не работает - если получение блокировки когда-либо завершится неудачно, будет сгенерировано неправильное исключение (поскольку код попытается снять блокировку, которую он никогда не получал), или, что еще хуже, если это рекурсивная блокировка, он будет выпущен рано. Правильный код выглядит так:
lock.acquire()
try:
# do stuff
finally:
# If lock.acquire() fails, this *doesn't* run
lock.release()
Используя оператор with
, невозможно ошибиться, так как он встроен в менеджер контекста:
with lock: # The lock *knows* how to correctly handle acquisition and release
# do stuff
Другое место, где очень полезен оператор with
, аналогично главному преимуществу декораторов функций и классов: он принимает код из двух частей, который может быть разделен произвольным числом строк кода (определение функции для декораторов - блок try
в текущем случае) и превращает его в «цельный» код, где программист просто объявляет, что он пытается сделать.
Для коротких примеров это не выглядит большим выигрышем, но на самом деле это имеет огромное значение при просмотре кода. Когда я вижу lock.acquire()
в куске кода, мне нужно прокрутить вниз и проверить соответствующий lock.release()
. Когда я вижу with lock:
, такая проверка не требуется - я сразу вижу, что блокировка будет снята правильно.