изменения внутри try-except persist после исключения

Поскольку я впервые узнал об обработке исключений (не в Python), у меня создалось впечатление, что когда вы начинаете блок try , это похоже на то, что вы начинаете писать в песочнице: если возникает исключение, все, что произошло внутри попробуйте блок, как никогда не было. К моему наивному удивлению, я заметил, что это неправда или не так, как я думал, по крайней мере, на Питоне. Вот мой эксперимент в Python:

>>> a = range(5) >>> a [0, 1, 2, 3, 4] >>> try: ... a.append(5) ... oops ... except: ... raise ... Traceback (most recent call last): File "<stdin>", line 3, in <module> NameError: name 'oops' is not defined >>> print a [0, 1, 2, 3, 4, 5] 

Как вы можете видеть, я изменил список внутри блока try , а затем вызвал ошибку, которая была поднята. Я ожидал увидеть список в его первоначальной форме [0, 1, 2, 3, 4] , но a.append(5) сохранился.

Были ли мои ожидания ошибочными в первую очередь? Возможно, частично неправильные ожидания (может быть песочница, но это не так)?

Вы только что обнаружили, что исключения не являются серебряной пулей для обработки ошибок.

Исключения не защищают вас от изменений состояния … все, что было выполнено без ошибок до того, как выбрано исключение, должно быть отменено. Вот как работает исключение в Python, C ++, Java и многих других языках.

Иногда у вас может быть какая-то «внешняя» общая защита: например, если все, что вы делаете, это изменения в базе данных, которая поддерживает транзакции, тогда вы можете заставить оператор верхнего уровня вызывать «откат» вместо того, чтобы совершать изменения, и вы получите защиту, которую вы ищете. Если нет такой естественной «стены», защищающей от частичного изменения состояния, тогда с ней гораздо труднее справиться.

Сама причина, что операции, которые были завершены, не будет отменена, – это то, что делает использование исключений тривиальным, поскольку сложность проблем масштабируется.

Обычно код может быть классифицирован как «безопасный» на нескольких уровнях:

  1. В случае исключения все разрушается, и даже возможен чистый выход или перезапуск. Это то, что обычно классифицируется как НЕ исключение.

  2. В случае исключения код не завершит свое задание, а состояние подсистемы (экземпляр класса, библиотека) недействительно. Однако вы можете безопасно перезапустить (например, вы можете уничтожить экземпляр или повторно инициализировать библиотеку). Это минимальная безопасность исключений.

  3. В случае исключения код не выполнит свое задание, и состояние подсистемы будет действительным, но неуказанным. Вызывающий код может попытаться продолжать использовать подсистему вместо повторной инициализации. Просто немного лучше, чем 2.

  4. В случае исключения код ничего не сделает, оставив состояние программы нетронутым. Таким образом, либо запрос завершается без ошибок, либо сигнал ошибки возвращается вызывающему, и ничего не изменилось. Это, конечно, лучшее поведение.

Самая большая проблема с обработкой исключений заключается в том, что даже если у вас есть две очень безопасные части 4 и 4, все же простая последовательная композиция AB небезопасна, потому что в случае возникновения проблемы в B вы также должны отменить все, что A завершено. Кроме того, если вы можете получить исключение при выполнении 1/A (т. Е. Когда вы делаете то, что не удалось выполнить A ), вы попадаете в БОЛЬШУЮ проблему, потому что вы не можете ни делать B , ни восстанавливать состояние, как было раньше ( это означает, что реализовать AB как операцию типа 4 просто невозможно).

Другими словами, хорошие кирпичи не будут делать тривиальные создания хороших конструкций (в отношении безопасности исключений).

Ваше ожидание неверно в отношении любого языка, который поддерживает исключения – нет семантики «все или ничего», связанной с блоками try (хотя на некоторых языках могут существовать концепции транзакций, например, если есть поддержка транзакционной памяти).

Это только то, что часть блока try после исключения больше не выполняется.

Да, ваши предположения были неправильными. Однако одна хорошая вещь, которую вы можете сделать, – это очистить определенные типы, используя. См . Учебник Python .

Единственное место, где я видел вашу идею изолированных блоков, которые откатываются от ошибок, – это функциональные языки.

Ваши ожидания ошибочны. Возможно, это была бы приятная функция (она существует под транзакциями имен – например, в SQL), но она редко встречается на языках программирования, и большинство исследований происходит на тех языках, которые никогда не используются), но это невозможно на любом языке, который любой использует (гораздо менее эффективно – поскольку компилятор / интерпретатор в целом мало знает о том, что делает программист, вам нужно сохранить состояние всей программы и восстановить ее, и это все равно пропустит все побочные эффекты вне интерпретатора, например, ввод-вывод файлов).

Блок try просто означает « попробуйте сделать это, и пропустите это, если вы потерпите неудачу». Обратите внимание, что только вторая половина является специальной – если исключение происходит за пределами блока try , выполнение перескакивает график вызовов до следующей try или – если их нет – для какого-либо глобального обработчика, который печатает исключение и завершает выполнение программа.