Как продолжить выполнение кадра из последней попытки инструкции после обработки исключения?

Я хотел бы обработать исключение NameError, введя нужную недостающую переменную в кадр и затем продолжить выполнение из последней попытки.

Следующий псевдокод должен иллюстрировать мои потребности.

def function():
    return missing_var

try:
    print function()

except NameError:

    frame = inspect.trace()[-1][0]

    # inject missing variable
    frame.f_globals["missing_var"] = ...

    # continue frame execution from last attempted instruction
    exec frame.f_code from frame.f_lasti

Прочитайте весь unittest на repl.it

Примечания

Фон

Код работает в подчиненном процессе, который контролируется родителем. Задачи (функции действительно) записываются в родительском, а последние передаются подчиненному устройству с помощью dill. Я ожидаю, что некоторые задачи (работающие в подчиненном процессе) попытаются получить доступ к переменным из внешних областей в родительском элементе, и я хотел бы, чтобы подчиненный запрашивал эти переменные родителям на лету.

p.s.: Я не ожидаю, что эта магия будет работать в рабочей среде.

Ответ 1

В отличие от того, что говорят разные комментаторы, в Python возможна обработка исключений "возобновление на ошибку". Библиотека fuckit.py реализует указанную стратегию. Это ошибки паролей, переписывая исходный код вашего модуля во время импорта, вставляя try...except блокирует каждое утверждение и проглатывая все исключения. Так что, возможно, вы могли бы попробовать подобную тактику?

Само собой разумеется: библиотека предназначена как шутка. Никогда не используйте его в производственном коде.


Вы упомянули, что ваш прецедент - это привязка ссылок на недостающие имена. Вы думали об использовании метапрограммирования для запуска своего кода в контексте "умного" пространства имен, такого как defaultdict? (Это, пожалуй, чуть менее плохая идея, чем fuckit.py.)

from collections import defaultdict

class NoMissingNamesMeta(type):
    @classmethod
    def __prepare__(meta, name, bases):
        return defaultdict(lambda: "foo")

class MyClass(metaclass=NoMissingNamesMeta):
    x = y + "bar"  # y doesn't exist

>>> MyClass.x
'foobar'

NoMissingNamesMeta представляет собой metaclass - конструкцию языка для настройки поведения оператора class. Здесь мы используем метод __prepare__ для настройки словаря, который будет использоваться как пространство имен классов во время создания класса. Таким образом, поскольку мы используем defaultdict вместо обычного словаря, класс, метакласс которого NoMissingNamesMeta никогда не получит NameError. Любые имена, упомянутые при создании класса, будут автоматически инициализированы до "foo".

Этот подход аналогичен идее @AndréFratelli о том, что вручную запрашивает лениво инициализированные данные из объекта Scope. В производстве я бы сделал это, а не это. Версия метакласса требует меньше ввода текста для написания кода клиента, но за счет гораздо большего количества магии. (Представьте себе, что вы отлаживаете этот код через два года, пытаясь понять, почему несуществующие переменные динамически вносятся в сферу!)

Ответ 2

Методика обработки исключений "возобновления" оказалась проблематичной, почему она отсутствует на языках С++ и более поздних версий.

Лучше всего использовать цикл while, чтобы не возобновить, где было исключено исключение, а скорее повторить из заданного места:

while True:
    try:
        do_something()
    except NameError as e:
        handle_error()
    else:
        break

Ответ 3

Вы действительно не можете размотать стек после исключения, поэтому вам придется иметь дело с проблемой перед вами. Если ваше требование состоит в том, чтобы генерировать эти переменные "на лету" (что не рекомендуется, но, похоже, вы это понимаете), тогда вам нужно будет их действительно запросить. Вы можете реализовать механизм для этого (например, иметь глобальный пользовательский экземпляр класса Scope и переопределять __getitem__ или использовать что-то вроде __dir__), но не так, как вы просите об этом.