Хочу обратить внимание на маленькую особенность написания конструкции 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()
Надеюсь, последний пример запомнится хорошо и внесёт ясность в головы уважаемых молодых коллег.
Автор: Andrew Svetlov