Хочу обратить внимание на маленькую особенность написания конструкции try-finally.
Возьмём для примера многопоточность, а конкретно блокировки.
Где-то (наверное, в конструкторе класса) мы создали объект блокировки:
self.locker = threading.RLock()
Затем в каком-то методе мы пытаемся использовать эту блокировку в try-finally statement. Да, я знаю что RLock поддерживает context manager protocol и может использоваться в with statement. Так будет даже лучше, но мы сейчас говорим о другом варианте использования.
try:
self.locker.acquire()
do_some_work()
finally:
self.locker.release()
В чём ошибка? .acquire()
может выбросить исключение. Блокировка не
будет захвачена и попытка её освободить в .release()
выбросит новое
(другое) исключение. Что крайне нежелательно. Особенно в python 2.x,
где нет цепочек исключений. Т.е. ошибка в .acquire()
будет просто
скрыта, понять в чём было дело невозможно.
Правильно писать так:
self.locker.acquire()
try:
do_some_work()
finally:
self.locker.release()
Если было исключение в .acquire()
— то блокировка не захвачена и
освобождать её не нужно. Пусть обработка исключения разворачивается
своим ходом, .release()
в finally block совершенно не нужен.
Правило простое и понятное, тем не менее я сам нередко писал ошибочный код. А сегодня опять увидел это проблему при чтении чужих исходников.
Проблема усугубляется тем, что обычно .acquire()
работает успешно, и
лишь в редких случаях выбрасывает исключение. Которое мы видим в логах
(все используют логи, верно?) и недоумеваем, что именно произошло.
Это замечание относится к любому коду, выполняемому в finally block.
Переменные, блокировки, захват ресурсов, открытие файлов и т.д. должны быть выполнены перед try.
P.S.
На открытие файлов хочу обратить особое внимание как на самый частый случай. Куда более частый чем работа с многопоточностью. Правильно писать:
f = open('filename')
try:
f.read()
finally:
f.close()
Надеюсь, последний пример запомнится хорошо и внесёт ясность в головы уважаемых молодых коллег.
Это, кстати, актуально не только для Python.
ОтветитьУдалитьЭто актуально для всех языков, имеющих конструкцию try-finally.
УдалитьНо я пишу о своём любимом Python, поэтому и текст соответствующий.
При работе с файлом я бы написал:
ОтветитьУдалитьwith open('filename') as f:
pass
with statement намного лучше, как я уже писал. Но статья не о with, а о том как использовать try-finally правильно.
УдалитьПример с open():
ОтветитьУдалитьopen(name[, mode[, buffering]])
Open a file, returning an object of the file type described in section File Objects. If the file cannot be opened, IOError is raised.
f = open('filename') <--- если здесь будет IOError, то весь код ниже будет бесполезен, нет?
Либо я не понимаю, чего хочется добиться используя try-finally
В try-finally нужно закрыть уже открытый файл если работе с ним была ошибка. Если open выбросил IOError то файл не открыт и в finally его закрывать не нужно.
УдалитьСпасибо большое за пример try-finally с файлом.
ОтветитьУдалитьМожно теперь починить свой костыльный код =)
Пожалуйста
Удалить