Комментарии 10
А зачем интерпретатор что-то сохраняет когда доходит до try? Он же в except вам даст состояние программы не до try, а до исключения
Хороший вопрос, и у меня нет на него ответа. Но чтоб было о чем подискутировать я приведу несколько цитат из Луца, которые засели у меня в голове при повторном его прочтении.
Как работают операторы try
С точки зрения работы ниже описано, как выполняются операторы try. При входе в оператор try интерпретатор Python запоминает текущий контекст программы, чтобы он мог возвратиться к нему, если возникнет исключение. Первыми выполняются операторы, вложенные внутрь заголовка try. То, что происходит следующим, зависит от того, генерировались ли исключения во время выполнения операторов блока try, и соответствуют ли они тем, которые отслеживает try.
• Если исключение происходит во время выполнения операторов блока try и оно соответствует одному из перечисленных в операторе, тогда интерпретатор Python переходит обратно на try и запускает операторы под первой конструкцией except, дающей совпадение со сгенерированным исключением. Затем объект сгенерированного исключения присваивается переменной, указанной после ключевого слова as в конструкции (при его наличии). После выполнения блока except поток управления возобновляется ниже полного оператора try (если только сам блок except не сгенерирует еще одно исключение, в случае чего процесс начинается заново с этой точки в коде).
5 издание том 2 Часть VII глава 34 стр 328
Обратите внимание, что в Python отсутствует способ возвратиться обратно к коду, который сгенерировал исключение (конечно, не считая повторного запуска кода, достигнувшего данной точки). Как только вы перехватили исключение, поток управления продолжается после полного оператора try, перехватившего исключение, но не после оператора, его инициировавшего. На самом деле Python очищает память от любых функций, которые завершили работу в результате возникновения исключения, подобных функции fetcher в нашем примере; они не возобновляемы. Оператор try перехватывает исключения и является тем местом, где программа возобновляет выполнение.
5 издание том 2 Часть VII глава 33 стр 320
Однако подлинный возврат к предыдущему состоянию не является частью языка Python. Возврат к предыдущему состоянию перед переходом отменяет все вычисления, но исключения Python этого не делают: переменные, которым присваивались значения между моментом входа в оператор try и моментом генерации исключения, не переустанавливаются в свои предыдущие значения. Даже генераторные функции и выражения, обсуждаемые в главе 20 первого тома, не делают полный возврат к предыдущему состоянию — они реагируют на запросы next(G) просто восстановлением состояния и возобновлением выполнения.
5 издание том 2 Часть VII глава 33 стр 318
Проблема отсылки к Луцу в том, что когда он писал книгу актуальный был Python 3.4, а с тех пор много чего поменялось. К сожалению я не нашел в доке информации подобной этой.
Кроме except есть ещё ошибочно названный try default.
В контексте C++ уместно было бы упомянуть идиому Resource Acquisition Is Initialization (RAII). Мне кажется, что идеологически это ближе к context managers, чем try....catch. Идея в том, что ресурс (динамическия память, открытый файл и т.п.) привязывается к объекту, который корректно освобождает ресурс при выходе из области видимости
Тогда пример на python:
with open('a.txt', 'r') as f:
# do something with the file f
Аналогичен такому коду на C++:
std::ifstream f('a.txt');
// do something with the file f
При выходе из области видимости файл будет закрыт при вызове деструктора объёкта std::ifstream
Ну только вот из деструктора исключениями не покидаться.
А без исключений он гарантированно закроет файл?
Да мне честно говоря не особо интересно что там с файлом происходит. Важно что закрытие умеет вернуть ошибку, а мы из деструктора не можем ничего внятного с ней сделать.
Я имел в виду что в общем случае RAII из C++ не позволяет делать то что можно делать через with
. Ну допустим мы хотим через with
начать транзакцию в БД, что-то там поделать и попытаться её закоммитить при выходе из with
. И это совершенно нормально что коммит может не удаться и мы выкинем наружу исключение. Но в случае C++ и RAII, мы не можем из деструктора ни кидать исключения, ни возвращать значения. В результате у нас нет никакого способа сообщить вызывающей стороне что коммит не удался.
Всем доброго дня! Большое спасибо за статью и обсуждение! Но вообще вместо: «в общем случае RAII из C++ не позволяет делать то, что можно делать через with» корректнее сказать, что область применения with шире, чем RAII. Ваш пример про трансакцию не очень уместно рассматривать в контексте RAII – трансакция не является ресурсом (вот соединение да, ресурс).
А автору статьи действительно стоит указать, что with позволяет изящно реализовать идиому RAII (в C++ ресурс освобождается в деструкторе, в Python из-за сборщика мусора аналогичным способом действовать не получится, но спасает блок finally, который «прячется» в with?) .
И наверно не стоит так часто использовать термин «синтаксический сахар». Всё-таки with – это нечто большее, поскольку влияет на стиль мышления программиста, побуждая думать в логике контекста, что приводит к таким красивым обобщениям, как trio (и, соответственно, вдохновленный trio TaskGroup из asynсio).
В принципе добавить можно. Но в С++ мой уровень компетенции ниже чем в Python. Поэтому стараюсь добавлять только то что верефицировано.
Под капотом Python. Тонкости популярных конструкций with и contextmanager