Возможно ли программно сконструировать фрейм стека Python и начать выполнение в произвольной точке кода?

StackOverflow https://stackoverflow.com/questions/541329

Вопрос

Возможно ли программно создать стек (один или несколько фреймов стека) в CPython и запустить выполнение в произвольной кодовой точке?Представьте себе следующий сценарий:

  1. У вас есть движок workflow, где рабочие процессы могут быть написаны на Python с помощью некоторых конструкций (напримерветвление, ожидание / присоединение), которые являются вызовами механизма workflow engine.

  2. Блокирующий вызов, такой как ожидание или соединение, устанавливает условие прослушивания в механизме диспетчеризации событий с каким-либо постоянным резервным хранилищем.

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

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

  5. Тем временем механизм workflow engine может быть остановлен и запущен повторно, что означает, что должна быть возможность программного сохранения и восстановления контекста сценария workflow.

  6. Механизм диспетчеризации событий запускает событие, которое распознает условие ожидания.

  7. Обработчик рабочего процесса считывает сериализованное состояние и стек и восстанавливает поток с помощью стека.Затем он продолжает выполнение в точке, где была вызвана служба ожидания.

Этот Вопрос

Можно ли это сделать с помощью неизмененного интерпретатора Python?Еще лучше, может ли кто-нибудь указать мне на какую-нибудь документацию, которая могла бы охватывать такого рода вещи, или пример кода, который программно создает фрейм стека и запускает выполнение где-то в середине блока кода?

Редактировать: Чтобы уточнить "неизмененный интерпретатор python", я не возражаю против использования C API (достаточно ли информации в PyThreadState для этого?), Но я не хочу копаться во внутренних компонентах интерпретатора Python и создавать модифицированный.

Обновить: Из некоторого первоначального исследования можно получить контекст выполнения с помощью PyThreadState_Get().Это возвращает состояние потока в PyThreadState (определенный в pystate.h), который имеет ссылку на фрейм стека в frame.Фрейм стека хранится в структуре, заданной по типу PyFrameObject, который определен в frameobject.h. PyFrameObject имеет поле f_lasti (реквизит для бобинс), который имеет программный счетчик, выраженный как смещение от начала блока кода.

Последнее в некотором роде хорошая новость, потому что это означает, что до тех пор, пока вы сохраняете фактический скомпилированный блок кода, вы должны иметь возможность восстанавливать локальные файлы для стольких кадров стека, сколько необходимо, и перезапускать код.Я бы сказал, это означает, что теоретически это возможно без необходимости создавать модифицированный интерпретатор python, хотя это означает, что код по-прежнему, вероятно, будет сложно и тесно связан с конкретными версиями интерпретатора.

Тремя оставшимися проблемами являются:

  • Состояние транзакции и откат 'saga', который, вероятно, может быть выполнен с помощью взлома метакласса, который можно было бы использовать для создания O / R mapper.Однажды я действительно создал прототип, так что у меня есть четкое представление о том, как это может быть достигнуто.

  • Надежная сериализация состояния транзакции и произвольных локальных параметров.Это может быть достигнуто путем чтения __locals__ (который доступен из фрейма стека) и программно создает вызов pickle.Однако я не знаю, какие здесь могут быть подводные камни, если таковые вообще имеются.

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

Обновление 2: PyCodeObject (code.h) имеет список addr (f_lasti)-> сопоставления номеров строк в PyCodeObject.co_lnotab (поправьте меня, если здесь я ошибаюсь).Это может быть использовано для облегчения процесса миграции для обновления рабочих процессов до новой версии, поскольку замороженные указатели команд могут быть сопоставлены соответствующему месту в новом скрипте, выполняемому в терминах номеров строк.Все еще довольно грязно, но немного более многообещающе.

Обновление 3: Я думаю, что ответом на это могло бы быть Питон без стека. Вы можете приостановить выполнение задач и сериализовать их.Я еще не выяснил, будет ли это работать и со стеком.

Это было полезно?

Решение 5

Со стандартным CPython это осложняется смешением данных C и Python в стеке.Перестройка стека вызовов потребовала бы одновременного восстановления стека C.Это действительно помещает его в слишком сложную корзину, поскольку потенциально может жестко привязать реализацию к конкретным версиям CPython.

Stackless Python позволяет настраивать тасклеты, что дает большую часть необходимых возможностей "из коробки".

Другие советы

Привязки expat python, включенные в обычный дистрибутив Python, программно создают фреймы стека.Однако имейте в виду, что он полагается на недокументированные и частные API.

http://svn.python.org/view/python/trunk/Modules/pyexpat.c?rev=64048&view=auto

То, что вы обычно хотите, - это продолжения, которые, как я вижу, уже являются тегом по этому вопросу.

Если у вас есть возможность работать со всем кодом в системе, вы можете попробовать сделать это таким образом, а не иметь дело с внутренними компонентами стека интерпретатора.Я не уверен, насколько легко это будет сохранено.

http://www.ps.uni-sb.de /~duchier/python/continuations.html

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

Другими словами:создайте свой собственный стек на уровне приложения.

Stackless python, вероятно, лучший ... если вы не возражаете полностью перейти на другой дистрибутив python. stackless может сериализовать все на python, плюс их тасклеты.Если вы хотите остаться в стандартном дистрибутиве python, то я бы использовал укроп, который может сериализовать почти все, что угодно на python.

>>> import dill
>>> 
>>> def foo(a):
...   def bar(x):
...     return a*x
...   return bar
... 
>>> class baz(object):
...   def __call__(self, a,x):
...     return foo(a)(x)
... 
>>> b = baz()
>>> b(3,2)
6
>>> c = baz.__call__
>>> c(b,3,2)
6
>>> g = dill.loads(dill.dumps(globals()))
>>> g
{'dill': <module 'dill' from '/Library/Frameworks/Python.framework/Versions/7.2/lib/python2.7/site-packages/dill-0.2a.dev-py2.7.egg/dill/__init__.pyc'>, 'c': <unbound method baz.__call__>, 'b': <__main__.baz object at 0x4d61970>, 'g': {...}, '__builtins__': <module '__builtin__' (built-in)>, 'baz': <class '__main__.baz'>, '_version': '2', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x4d39d30>, '__doc__': None}

Dill регистрирует свои типы в pickle реестра, поэтому, если у вас есть какой-то код черного ящика, который использует pickle и вы на самом деле не можете отредактировать его, тогда простой импорт dill может волшебным образом заставить его работать без исправления стороннего кода.

Вот dill маринование всей сессии переводчика...

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
dude@sakurai>$ python
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('foobar.pkl')
>>> c(b,3,2)
6

dill также имеет несколько хороших инструментов за то, что помог вам понять, что приводит к сбою вашей настройки при сбое вашего кода.

Вы также спрашивали, где это используется для сохранения состояния интерпретатора?

IPython ( Ипифон ) может использовать dill чтобы сохранить сеанс интерпретатора в файл. https://nbtest.herokuapp.com/github/ipython/ipython/blob/master/examples/parallel/Using%20Dill.ipynb

клептоман использование dill поддерживать кэширование в памяти, на диске или в базе данных, что позволяет избежать повторных вычислений. https://github.com/uqfoundation/klepto/blob/master/tests/test_cache_info.py

мистический использование dill сохранить контрольные точки для больших заданий оптимизации, сохранив состояние оптимизатора по мере его выполнения. https://github.com/uqfoundation/mystic/blob/master/tests/test_solver_state.py

Есть пара других пакетов, которые используют dill для сохранения состояния объектов или сеансов.

Вы могли бы захватить существующий кадр стека, создав исключение и отступив на один кадр назад в трассировке.Проблема в том, что не предусмотрено способа возобновить выполнение в середине (frame.f_lasti) блока кода.

“Возобновляемые исключения” - действительно интересная языковая идея, хотя сложно придумать разумный способ, которым они могли бы взаимодействовать с существующими блоками Python ‘try / finally’ и ‘with’.

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

Мне нужно решить проблему такого же типа.Интересно, что решил сделать оригинальный плакат.

stackless утверждает, что может обрабатывать тасклеты до тех пор, пока нет связанного с ними "обремененного" стека C ("обремененный" - это мой выбор формулировки).

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

Как насчет использования вакансия?

Я не совсем уверен, что это то, чего вы хотите, но, похоже, это соответствует идее создания рабочего процесса, этапы которого могут быть сохранены.Вариант использования Joblib, похоже, заключается в том, чтобы избежать повторных вычислений, я не уверен, это то, что вы пытаетесь сделать здесь, или что-то более сложное?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top