代码之家  ›  专栏  ›  技术社区  ›  ConcernedOfTunbridgeWells

是否可以以编程方式构造一个python堆栈框架并在代码中的任意点开始执行?

  •  22
  • ConcernedOfTunbridgeWells  · 技术社区  · 15 年前

    是否可以在cpython中以编程方式构造堆栈(一个或多个堆栈帧),并在任意代码点开始执行?想象一下下面的场景:

    1. 您有一个工作流引擎,在该引擎中,可以使用调用工作流引擎的一些构造(例如分支、等待/加入)在python中编写工作流脚本。

    2. 阻塞调用(如wait或join)在事件调度引擎中设置侦听器条件,并使用某种类型的持久备份存储。

    3. 您有一个工作流脚本,它调用引擎中的等待条件,等待稍后将发出信号的某个条件。这将在事件调度引擎中设置侦听器。

    4. 工作流脚本的状态、相关的堆栈帧(包括程序计数器(或等效状态))将被持久化,因为等待条件可能在数天或数月后发生。

    5. 在此期间,工作流引擎可能会停止并重新启动,这意味着必须能够以编程方式存储和重建工作流脚本的上下文。

    6. 事件调度引擎触发等待条件接收到的事件。

    7. 工作流引擎读取序列化状态和堆栈,并用堆栈重建线程。然后在调用等待服务的点继续执行。

    问题

    这可以用未修改的python解释器完成吗?更好的是,是否有人能给我指出一些可能涉及这类事情的文档,或者一个以编程方式构造堆栈框架并在代码块中间某个位置开始执行的代码示例?

    编辑: 为了澄清“未修改的python解释器”,我不介意使用C API(pythreadstate中是否有足够的信息来执行此操作?)但是我不想去探究一下Python解释器的内部结构,并且需要构建一个修改过的解释器。

    更新: 从一些初步的调查中,可以得到执行上下文 PyThreadState_Get() . 这将返回线程状态 PyThreadState (定义在 pystate.h ,其中引用了中的堆栈帧 frame . 堆栈帧保存在typedef'd到的结构中 PyFrameObject ,其定义见 frameobject.h . 吡虫啉对象 有一个领域 f_lasti (道具) bobince )它有一个程序计数器,表示为从代码块开始的偏移量。

    最后这是一个好消息,因为这意味着只要保留实际编译的代码块,就应该能够根据需要为尽可能多的堆栈帧重建局部变量,并重新启动代码。我想说,这意味着理论上不需要修改python interpereter就可以实现,尽管这意味着代码可能仍然会与解释器的特定版本紧密耦合。

    剩下的三个问题是:

    • 事务状态和“saga”回滚,这可能是通过一种用于构建O/R映射器的元类黑客攻击来完成的。我曾经构建过一个原型,所以我对如何实现这一目标有了一个大致的了解。

    • 对事务状态和任意局部变量进行严格的序列化。这可以通过阅读来完成。 __locals__ (可从堆栈帧获得)并通过编程构造对pickle的调用。但是,我不知道这里可能有什么,如果有的话。

    • 工作流的版本控制和升级。这有点棘手,因为系统没有为工作流节点提供任何符号锚。我们只有锚了 为了做到这一点,必须确定所有入口点的偏移量,并将它们映射到新版本。手动操作可能可行,但我怀疑很难实现自动化。如果您想支持此功能,这可能是最大的障碍。

    更新2: PyCodeObject ( code.h )有地址列表( 法拉斯蒂 )->中的行号映射 PyCodeObject.co_lnotab (如果这里有错,请纠正我)。这可能用于促进将工作流更新到新版本的迁移过程,因为冻结的指令指针可以根据行号映射到新脚本中的适当位置。仍然很混乱,但有点前途。

    更新3: 我想答案可能是 Stackless Python. 您可以挂起任务并将其序列化。我还没弄清楚这是否也适用于堆栈。

    7 回复  |  直到 11 年前
        1
  •  10
  •   Johan Dahlin Idelic    15 年前

    标准python发行版中包含的expat python绑定正以编程方式构建堆栈帧。不过,需要注意的是,它依赖于未记录的和私有的API。

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

        2
  •  7
  •   Dave Stenglein    15 年前

    你通常想要的是延续,我看到的已经是这个问题的标签了。

    如果您能够处理系统中的所有代码,您可能需要尝试 这样做而不是处理解释器堆栈内部。我不确定这会有多容易被坚持。

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

    在实践中,我将构建您的工作流引擎,以便您的脚本将操作对象提交给管理器。经理可以在任何时候pickle一组操作,并允许 它们将被加载并再次开始执行(通过恢复提交操作)。

    换句话说:创建自己的应用程序级堆栈。

        3
  •  3
  •   Mike McKerns    11 年前

    如果您不介意完全转到不同的python发行版,那么stackless 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 注册表,所以如果您有一些黑盒代码使用 泡菜 你不能真的编辑它,然后仅仅导入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
    

    小茴香 也有 some good tools 帮助您理解当代码失败时,是什么导致了酸洗失败。

    您还要求在哪里保存解释器状态?

    IPython 可以使用 小茴香 将解释器会话保存到文件中。 https://nbtest.herokuapp.com/github/ipython/ipython/blob/master/examples/parallel/Using%20Dill.ipynb

    klepto 使用 小茴香 支持内存、磁盘或数据库缓存,避免重新计算。 https://github.com/uqfoundation/klepto/blob/master/tests/test_cache_info.py

    mystic 使用 小茴香 通过在进行中保存优化器的状态来保存大型优化作业的检查点。 https://github.com/uqfoundation/mystic/blob/master/tests/test_solver_state.py

    还有一些其他的包可以使用 小茴香 保存对象或会话的状态。

        4
  •  2
  •   bobince    15 年前

    您可以通过抛出一个异常并在跟踪中向后退一帧来获取现有的堆栈帧。问题是没有办法在代码块的中间(frame.f_lasti)恢复执行。

    _可恢复的异常_是一个非常有趣的语言想法,尽管很难想到一种合理的方式,它们可以与Python现有的_ Try/Finally__和_____块进行交互。

    目前,实现这一点的正常方法只是使用线程在其控制器的单独上下文中运行工作流。(或者,如果您不介意编译它们的话,可以使用coroutines/greenlets)。

        5
  •  2
  •   ConcernedOfTunbridgeWells    13 年前

    对于标准的c python,这一点很复杂,因为堆栈中混合了C和python数据。重建调用堆栈需要同时重建C堆栈。这真的把它放在了一个过于困难的篮子里,因为它可能将实现与特定版本的cpython紧密地结合在一起。

    Stacklesspython允许微线程被pickle,这提供了开箱即用所需的大部分功能。

        6
  •  1
  •   Brad Clements    15 年前

    我有同样类型的问题要解决。我想知道原来的海报是怎么决定的。

    Stackless声称只要没有关联的“累赘的”C堆栈(累赘是我选择的措辞),它就可以腌制微线程。

    我可能会使用eventlet并找出某种方式来处理“状态”,但我真的不想写一个显式的状态机。

        7
  •  1
  •   lost    11 年前

    如何使用 joblib ?

    我不太确定这是否是你想要的,但它似乎符合这样一个想法:拥有一个可以持久化阶段的工作流。Joblib的用例似乎是为了避免重新计算,我不确定这是您在这里要做的,还是更复杂的事情?