Только что прочитал у Pythy, что вышел Python 2.5. В нем много всего нового и змечательного, и что приятно, я даже не нашел на первый взгляд ничего, что вызвало бы отрицательную реакцию.
Но я больше всего рад появлению одной конкретной вещи — оператора with
.
with
в Delphi
Я очень любил оператор with
, когда активно писал на Delphi, даже несмотря на опасность попутать переменные из разных областей видимости. Во-первых, потому что удобство от неписания постоянно одного и то же имени переменной у меня перекрывало редкие случаи багов, но главное, потому что он обладает очень четкой узнаваемой семантикой: локальный блок операций надо одним главенствующим объектом.
Из этой семантики естественным образом вытекает то, что with
удачно сочетается с блоком try..finally
, который гарантирует правильное создание и уничтожение объекта:
With TFileStream.Create(FileName) Do
Try
{ ... }
Finally
Free;
End;
И можно обойтись без объявления переменной.
Я, кстати, именно так без отступа писал try..finally
, чтобы подчеркнуть, что эта конструкция составляет с with
одно логическое целое.
with
в Питоне
И вот именно потому мне так и нравится новый питоновский with
, что он реализует как раз такое поведение: не просто блок с "главным" объектом, но еще и с автоматической его инициализацией и гарантированным завершением. То есть вся эта катавасия с громоздким try..finally
сведена в короткую синтаксическую конструкцию:
with open(filename) as f: # файл открывается
f.read(100)
# файл закрывается, даже после exception'ов
или
with threading.Lock():
# код критической секции
# lock освободится после выхода из with
Определение того, что именно происходит при инициализации и завершении, сделано чисто по-питоновски: методами __enter__()
и __exit__()
объекта, который стоит под with
. То есть можно легко делать и свои объекты, которые будут способны создать такой блок (который теперь называется контекстом, а объекты, соответственно, менеджерами контекста).
И что еще приятно! Той самой опасной особенности паскалевского with
в Питоне как раз и нет :-). Переменную, которая представляет объект в контексте, нужно указывать явно.
Вложенные try..finally
Еще одна штука, которой сильно не хватало в Delphi — это красивая синтаксическая возможность управлять уничтожением нескольких объектов.
Простой пример — блок кода, который работает с двумя файлами. Если написать так:
InFile := TFileStream.Create('...', fmOpenRead);
OutFile := TFileStream.Create('...', fmOpenWrite);
Try
{ процесс }
Finally
InFile.Free;
OutFile.Free;
End;
... то это, вообще-то, будет некорректно: при ошибке на второй строчке, которая идет до try
, finally
никогда не выполнится, и первый файл останется открытым. Поэтому более корректным будут два вложенных блока:
InFile := TFileStream.Create('...', fmOpenRead);
Try
OutFile := TFileStream.Create('...', fmOpenWrite);
Try
{ процесс }
Finally
OutFile.Free;
End;
Finally
InFile.Free;
End;
Но это уже дикость нечитаемая :-(.
В Питоне 2.5 для оформления таких вещей появилась библиотечная функция:
from contextlib import nested
with nested(open('...', 'r'), open('...', 'w')) as (in_file, out_file):
# процесс
# по выходу гарантирует закрытие и одного, и другого файлов
Ну и еще там есть всякие полезные утилитки, которые упрощают создание своих менеджеров контектса, используя распространенные паттерны и соглашения языка (как например то, что у подавляющего большинства объектов, которые можно "закрыть", этот метод называется close()
и никак иначе).
Эх... Приятно все-таки работать с языком, который так хорошо ложится на мозги!
Комментарии: 13
Одно не могу понять: зачем вводить слова
__enter__()
и__exit__()
, когда достаточно обойтись del'ом ?Эта пара методов позволяет не привязывать время жизни объекта к
with
. Например, если есть какой-то большой и сложный объект, который живет в программе от начала и до конца, но который в том числе может работать временами и как менеджер контекста.Вызов
__del__
не гарантируется при выходе изwith
, он вызовется когда-нибудь потом, когда будет удобно garbage collector'у. А закрывать файл или освобождать lock как раз и нужно обычно делать сразу и надежно.В
__exit__
передаются еще и параметры возникшего exception'а, если оно есть, чтобы его можно было обработать.А мне очень понравилась конструкция
x = true_value if condition else false_value
А почему бы не так:
?
Наверное имелся в виду Free вместо Close, но смысл понятен.
InFile и OutFile до начала этого блока содержат мусор. Поэтому если подорвется создание InFile, то OutFile так и не будет инициализирован, и OutFile.Free вероятнее всего вызовет access violation. А писать
до начала тоже не особенно приятно.
Ой... А я и в статье везде Close вместо Free понаписал :-). Все позабыл напрочь :-). Пошел исправлять...
Иван Сагалаев, да, действительно access violation. А Close был получен методом copy-paste :)
По мне так лучше access violation... чем допустить работать проге тогда когда файл не открыт и следовательно вся дальнейшая логика проги идёт к чертям собачьим...
По большей части именно оператор with заставил меня понять, что подход Питона — это путь в никуда. Даже удивлен, как в комменты не понабежали лисперы и не начали срать кирпичами, ибо with — традиционное семейство макросов лиспа для тех же целей, только реализуемых куда проще, чем питонов оператор.
Видимо, большинство лисперов более воспитанны. Кроме того, бесполезные комментарии я просто не пропускаю.