[英]Is it possible to programmatically construct a Python stack frame and start execution at an arbitrary point in the code?
是否可以在CPython中以編程方式構造堆棧(一個或多個堆棧幀)並在任意代碼點開始執行? 想象一下以下場景:
您有一個工作流引擎,其中工作流可以在Python中編寫腳本,其中包含一些構造(例如分支,等待/加入),這些構造是對工作流引擎的調用。
阻塞調用(例如等待或連接)在具有某種持久性后備存儲的事件調度引擎中設置偵聽器條件。
您有一個工作流腳本,它調用引擎中的等待條件,等待稍后將發出信號的某些條件。 這將在事件調度引擎中設置偵聽器。
工作流腳本的狀態,包括程序計數器(或等效狀態)的相關堆棧幀是持久的 - 因為等待條件可能在幾天或幾個月后發生。
在此期間,工作流引擎可能會停止並重新啟動,這意味着必須能夠以編程方式存儲和重新構建工作流腳本的上下文。
事件調度引擎觸發等待條件獲取的事件。
工作流引擎讀取序列化狀態並堆棧並使用堆棧重構線程。 然后在調用等待服務的點繼續執行。
問題
可以使用未修改的Python解釋器嗎? 更好的是,任何人都可以指向一些可能涵蓋此類事物的文檔或以編程方式構造堆棧幀並在代碼塊中間的某處開始執行的代碼示例嗎?
編輯:為了澄清'未修改的python解釋器',我不介意使用C API(在PyThreadState中是否有足夠的信息來執行此操作?)但是我不想去探討Python解釋器的內部並且有建立一個修改過的。
更新:從一些初步調查,可以使用PyThreadState_Get()
獲取執行上下文。 這將返回一個線程狀態PyThreadState
(定義在pystate.h
),其具有在堆棧幀的參考frame
。 堆棧幀在一個結構typedef定義到保持PyFrameObject
,其在定義frameobject.h
。 PyFrameObject
有一個字段f_lasti
(props to bobince ),它有一個程序計數器,表示為從代碼塊開頭的偏移量。
這最后是一個好消息,因為它意味着只要您保留實際編譯的代碼塊,您就應該能夠根據需要為盡可能多的堆棧幀重建本地,並重新啟動代碼。 我想說這意味着它在理論上是可能的,而不必制作一個修改過的python interpereter,雖然這意味着代碼仍然可能會非常繁瑣並與特定版本的解釋器緊密耦合。
剩下的三個問題是:
事務狀態和'saga'回滾,這可能是通過一種用於構建O / R映射器的元類黑客來完成的。 我確實曾經構建過一次原型,所以我對如何實現這一目標有了一個很好的了解。
強大地序列化事務狀態和任意本地。 這可以通過讀取__locals__
(可以從堆棧框架中獲得)並以編程方式構建對pickle的調用來實現。 但是,我不知道在那里可能有什么,如果有的話。
版本控制和升級工作流程。 這有點棘手,因為系統沒有為工作流節點提供任何符號錨。 我們所擁有的只是錨點為了做到這一點,人們必須確定所有入口點的偏移並將它們映射到新版本。 可能手動完成,但我懷疑自動化很難。 如果您想支持此功能,這可能是最大的障礙。
更新2: PyCodeObject
( code.h
)在PyCodeObject
有一個addr( f_lasti
) - >行號映射PyCodeObject.co_lnotab
(如果錯誤,請糾正我)。 這可以用於促進遷移過程以將工作流更新到新版本,因為凍結的指令指針可以映射到新腳本中的適當位置,根據行號完成。 仍然相當混亂,但更有希望。
更新3:我認為答案可能是Stackless Python。 您可以暫停任務並將其序列化。 我還沒有弄清楚這是否也適用於堆棧。
普通Python發行版中包含的expat 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中的所有內容 ,以及它們的tasklet。 如果你想留在標准的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就可以神奇地使它工作而不用monkeypatching第三方代碼。
這是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
來保存對象或會話的狀態。
您可以通過拋出異常並在回溯中退回一幀來抓取現有的堆棧幀。 問題是沒有辦法在代碼塊的中間(frame.f_lasti)恢復執行。
“可恢復的異常”是一個非常有趣的語言概念,盡管想到一種合理的方式可以與Python現有的“try / finally”和“with”塊進行交互是很棘手的。
目前,執行此操作的常規方法是使用線程在與其控制器不同的上下文中運行工作流。 (或者如果你不介意編譯它們的協同程序/ greenlets)。
使用標准的CPython,堆棧中的C和Python數據混合使這變得復雜。 重建調用堆棧需要同時重建C堆棧。 這真的把它放在太難的籃子里,因為它可能將實現緊密地耦合到特定版本的CPython。
Stackless Python允許對tasklet進行pickle,這提供了開箱即用所需的大部分功能。
我有同樣類型的問題需要解決。 我想知道原始海報決定做什么。
只要沒有相關的“阻礙”C堆棧(我選擇的措辭),它就可以腌制tasklet。
我可能會使用eventlet並找出一些酸洗'狀態'的方法,我真的不想寫一個顯式的狀態機雖然..
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.