Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

Вопрос

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

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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}

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

Здесь 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 также имеет несколько хороших инструментов, помогающих понять, что приводит к сбою вашего травления, когда ваш код не работает.

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

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

Ответ 6

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

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

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

Ответ 7

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

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