22

CPython でスタック (1 つまたは複数のスタック フレーム) をプログラムで構築し、任意のコード ポイントで実行を開始することは可能ですか? 次のシナリオを想像してください。

  1. ワークフロー エンジンへの呼び出しであるいくつかの構造 (分岐、待機/結合など) を使用して Python でワークフローをスクリプト化できるワークフロー エンジンがあります。

  2. wait や join などのブロッキング呼び出しは、ある種の永続的なバッキング ストアを使用して、イベント ディスパッチ エンジンでリスナー条件を設定します。

  3. エンジンで待機条件を呼び出し、後で通知される条件を待機するワークフロー スクリプトがあります。これにより、イベント ディスパッチ エンジンにリスナーが設定されます。

  4. ワークフロー スクリプトの状態、関連するスタック フレーム (プログラム カウンター (または同等の状態) を含む) は、数日後または数か月後に待機状態が発生する可能性があるため、保持されます。

  5. その間に、ワークフロー エンジンが停止され、再起動される可能性があります。つまり、ワークフロー スクリプトのコンテキストをプログラムで保存および再構築できる必要があります。

  6. イベント ディスパッチ エンジンは、待機条件がピックアップするイベントを発生させます。

  7. ワークフロー エンジンは、シリアル化された状態とスタックを読み取り、スタックを使用してスレッドを再構築します。次に、待機サービスが呼び出された時点で実行を継続します。

質問

これは、変更されていない Python インタープリターで実行できますか? さらに良いことに、この種のことをカバーする可能性のあるドキュメント、またはプログラムでスタック フレームを構築し、コード ブロックの途中で実行を開始するコードの例を誰かに教えてもらえますか?

編集:「変更されていない python インタープリター」を明確にするために、C API を使用してもかまいません (これを行うには PyThreadState に十分な情報がありますか?) が、Python インタープリターの内部を調べて、変更されたものを構築します。

更新:最初の調査から、実行コンテキストを取得できますPyThreadState_Get()。これは、のスタック フレームへの参照を持つPyThreadState(で定義された)のスレッド状態を返します。スタック フレームは、 で定義されている にtypedef された構造体に保持されます。 コードブロックの先頭からのオフセットとして表されるプログラムカウンターを持つフィールド(bobinceへの小道具)があります。pystate.hframePyFrameObjectframeobject.hPyFrameObjectf_lasti

これは、実際にコンパイルされたコード ブロックを保持している限り、必要な数のスタック フレームのローカルを再構築し、コードを再起動できることを意味するため、一種の朗報です。これは、変更された python インターペレタを作成しなくても理論的には可能であることを意味すると思いますが、コードはおそらく、インタープリタの特定のバージョンに厄介で緊密に結合される可能性があることを意味します。

残る問題は次の3つです。

  • トランザクション状態と 'saga' ロールバック。これは、O/R マッパーを構築するために使用するメタクラス ハッキングのようなものによっておそらく達成できます。私は一度プロトタイプを作成したので、これがどのように達成されるかについてかなりのアイデアを持っています.

  • トランザクション状態と任意のローカルを堅牢にシリアル化します。これは、読み取り__locals__(スタック フレームから利用可能) と、pickle への呼び出しをプログラムで作成することによって実現できます。ただし、ここに何があるのか​​ わかりません。

  • ワークフローのバージョン管理とアップグレード。システムはワークフロー ノードのシンボリック アンカーを提供していないため、これはやや複雑です。これを行うには、すべてのエントリ ポイントのオフセットを特定し、それらを新しいバージョンにマップする必要があります。おそらく手動で行うことは可能ですが、自動化するのは難しいと思います。この機能をサポートしたい場合、これがおそらく最大の障害になります。

更新 2: PyCodeObject ( code.h) には addr ( f_lasti)-> 行番号マッピングのリストがありPyCodeObject.co_lnotabます (ここで間違っている場合は訂正してください)。これは、ワークフローを新しいバージョンに更新するための移行プロセスを容易にするために使用される場合があります。これは、凍結された命令ポインターを新しいスクリプトの適切な場所にマップし、行番号に関して行うことができるためです。まだかなり面倒ですが、もう少し有望です。

更新 3:これに対する答えはStackless Python かもしれません。 タスクを一時停止してシリアル化できます。これがスタックでも機能するかどうかはわかりません。

4

7 に答える 7

10

通常の Python ディストリビューションに含まれる expat python バインディングは、プログラムでスタック フレームを構築しています。ただし、文書化されていないプライベート API に依存していることに注意してください。

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

于 2009-02-12T14:13:19.957 に答える
6

あなたが一般的に望んでいるのは継続であり、これはすでにこの質問のタグになっています。

システム内のすべてのコードを操作できる場合は、インタープリター スタックの内部を処理するのではなく、この方法を試してみることをお勧めします。これがどれほど簡単に永続化されるかはわかりません。

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

実際には、スクリプトがアクション オブジェクトをマネージャーに送信するように、ワークフロー エンジンを構築します。マネージャーは、任意の時点で一連のアクションをピクルし、(アクションの送信を再開することによって) ロードして実行を再開できるようにすることができます。

つまり、独自のアプリケーション レベルのスタックを作成します。

于 2009-02-12T16:59:17.187 に答える
3

スタックレス python はおそらく最高です.別の python ディストリビューションに完全に移行することを気にしない場合. python のすべてstacklessとそのタスクレットをシリアル化できます。標準の 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あり、実際には編集できない場合、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には、コードが失敗したときに酸洗が失敗する原因を理解するのに役立ついくつかの優れたツールもあります。

また、インタープリターの状態を保存するために使用される場所も尋ねましたか?

IPythondillは、インタープリター セッションをファイルに保存するために使用できます。https://nbtest.herokuapp.com/github/ipython/ipython/blob/master/examples/parallel/Using%20Dill.ipynb

kleptodill、再計算を回避するメモリ内、ディスクへ、またはデータベースへのキャッシュをサポートするために使用します。https://github.com/uqfoundation/klepto/blob/master/tests/test_cache_info.py

mysticdill、進行中のオプティマイザーの状態を保存することにより、大規模な最適化ジョブのチェックポイントを保存するために使用します。https://github.com/uqfoundation/mystic/blob/master/tests/test_solver_state.py

dillオブジェクトまたはセッションの状態を保存するために使用する他のパッケージがいくつかあります。

于 2014-01-25T13:04:32.403 に答える
2

例外をスローし、トレースバックで 1 フレーム戻ることで、既存のスタック フレームを取得できます。問題は、コード ブロックの途中 (frame.f_lasti) で実行を再開する方法が提供されていないことです。

「再開可能な例外」は非常に興味深い言語のアイデアですが、Python の既存の 'try/finally' および 'with' ブロックとやり取りできる合理的な方法を考えるのは難しいです。

今のところ、これを行う通常の方法は、スレッドを使用してワークフローをコントローラーとは別のコンテキストで実行することです。(または、コンパイルしてもかまわない場合は、コルーチン/グリーンレット)。

于 2009-02-12T14:37:18.730 に答える
2

標準の CPython では、スタック内に C と Python のデータが混在しているため、これは複雑です。呼び出しスタックを再構築するには、C スタックを同時に再構築する必要があります。これは、実装を特定のバージョンの CPython に密結合する可能性があるため、非常に難しいバスケットに入れられます。

スタックレス Python では、タスクレットをピクルすることができます。これにより、すぐに必要な機能のほとんどが提供されます。

于 2011-09-12T14:31:32.223 に答える
1

同じタイプの問題を解決する必要があります。元のポスターは何をしようと決めたのだろうか。

stackless は、関連する「邪魔された」C スタックがない限り、タスクレットをピクルできると主張しています (encumbered は私の言い回しの選択です)。

私はおそらくイベントレットを使用して、「状態」をピクルする方法を見つけますが、明示的な状態マシンを書きたくありません..

于 2009-10-06T02:48:16.313 に答える