exclude-clause удаляет локальную переменную

exc = None
try:
    raise Exception
except Exception as exc:
    pass

# ...

print(exc)

NameError: имя «exc» не определено

Раньше это работало в Python2. Почему это было изменено таким образом? Если бы я мог хотя бы переназначить exc, аналогично атрибутам уровня класса

class Foo(object):
    Bar = Bar

но это тоже не работает:

exc = None
try:
    raise Exception
except Exception as exc:
    exc = exc

Любые хорошие подсказки для достижения того же? Я бы предпочел не писать что-то вроде этого:

exc = None
try:
    raise Exception("foo")
except Exception as e:
    exc = e

# ...

print(exc)

person Niklas R    schedule 17.06.2014    source источник


Ответы (1)


Оператор try явно ограничивает область действия связанного исключения, чтобы предотвратить его утечку из-за циклических ссылок. См. документацию по try по оператору:

Когда исключение назначается с использованием в качестве цели, оно очищается в конце предложения исключения.

[...]

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

Акцент мой; обратите внимание, что ваш единственный вариант — привязать исключение к новому имени.

В Python 2 исключения не имели ссылки на трассировку, поэтому это было изменено.

Однако даже в Python 2 вас явно предупреждают об очистке трассировки, см. sys.exc_info():

Предупреждение. Присвоение возвращаемого значения traceback локальной переменной в функции, обрабатывающей исключение, вызовет циклическую ссылку. Это предотвратит сборку мусора на все, на что ссылается локальная переменная в той же функции или в трассировке. Поскольку большинству функций не требуется доступ к трассировке, лучшим решением будет использование чего-то вроде exctype, value = sys.exc_info()[:2] для извлечения только типа и значения исключения. Если вам нужна трассировка, обязательно удалите ее после использования (лучше всего сделать это с помощью оператора try ... finally) или вызовите exc_info() в функции, которая сама не обрабатывает исключение.

Если вы повторно привязываете исключение, вы можете явно очистить трассировку:

try:
    raise Exception("foo")
except Exception as e:
    exc = e
    exc.__traceback__ = None
person Martijn Pieters    schedule 17.06.2014