2009-02-12 7 views
22

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

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

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

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

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

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

  6. Событие диспетчерского двигателя запускает событие, которое ожидает условие ожидания.

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

Вопрос

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

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

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

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

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

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

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

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

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

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

+3

Отличный вопрос - я бы не стал ненавидеть того, кто должен отлаживать этот проект! –

ответ

2

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

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

10

Связи python, входящие в стандартный дистрибутив Python, строят фреймы стека программно. Однако будьте осторожны, он полагается на недокументированные и частные API.

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

+0

Спасибо. Очень полезно при разработке механизма работы. – ConcernedOfTunbridgeWells

2

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

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

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

7

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

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

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

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

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

+1

Что мне нравится в «объектах действия» (думаю: «Шаблон команды»), так это то, что это также обеспечит поддержку отката рабочего процесса. Хотя это компромисс против простоты и ясности сценария рабочего процесса, это хороший подход с этой точки зрения. – ConcernedOfTunbridgeWells

1

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

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

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

+0

К сожалению, ОП отложил проблему на данный момент, поскольку первоначальный проект так и не пошел:^p – ConcernedOfTunbridgeWells

1

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

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

2

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

Вот dill засолки всего сеанса интерпретатора ...

>>> # continuing from above 
>>> dill.dump_session('foobar.pkl') 
>>> 
>>> ^D 
[email protected]>$ 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 также some good tools для помогая вам понять, что вызывает у вас травления потерпеть неудачу, когда ваш код не удается.

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

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 для сохранения состояния объектов или сеансов.