Можно ли программно построить фрейм стека Python и начать выполнение в произвольной точке кода? - PullRequest
23 голосов
/ 12 февраля 2009

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

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

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

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

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

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

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

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

Вопрос

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

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

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

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

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

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

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

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

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

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

Ответы [ 7 ]

10 голосов
/ 12 февраля 2009

Связи Python-экспата, включенные в нормальный дистрибутив Python, конструируют стековые фреймы программно. Будьте осторожны, он опирается на недокументированные и частные API.

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

7 голосов
/ 12 февраля 2009

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

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

http://www.ps.uni -sb.de / ~ duchier / питон / continuations.html

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

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

3 голосов
/ 25 января 2014

Stackless python, вероятно, лучший ... если вы не против полностью перейти на другой дистрибутив Python. stackless может сериализовать все в python, плюс их тасклеты. Если вы хотите остаться в стандартном дистрибутиве Python, то я бы использовал dill , который может сериализовать почти что угодно в 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}

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

Вот 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

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

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

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

2 голосов
/ 12 сентября 2011

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

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

2 голосов
/ 12 февраля 2009

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

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

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

1 голос
/ 01 июля 2013

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

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

1 голос
/ 06 октября 2009

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

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

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

...