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

是否有可能以编程方式构造Python堆栈帧并在代码中的任意点开始执行?

  •  23
  • ConcernedOfTunbridgeWells  · 技术社区  · 16 年前

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

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

    2. 阻塞调用(如等待或加入)在具有某种持久后备存储的事件调度引擎中设置侦听器条件。

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

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

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

    6. 事件调度引擎触发等待条件所触发的事件。

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

    问题

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

    更新: 通过一些初步调查,可以获得执行上下文 PyThreadState_Get() 。这将返回线程状态 PyThreadState (定义见 pystate.h ),其中引用了堆栈帧 frame PyFrameObject frameobject.h . PyFrameObject 有一个字段 f_lasti (道具 bobince )其具有表示为与代码块开头的偏移的程序计数器。

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

    剩下的三个问题是:

    • 事务状态和“传奇”回滚,这可能可以通过构建O/R映射器时使用的元类黑客攻击来实现。我确实构建过一个原型,所以我对如何实现它有一个大致的想法。

    • __locals__

    更新2: PyCodeObject code.h )有一个地址列表( PyCodeObject.co_lnotab

    更新3: 我认为这个问题的答案可能是 Stackless Python.

    7 回复  |  直到 8 年前
        1
  •  10
  •   Johan Dahlin Idelic    16 年前

    普通python发行版中包含的expat-python绑定正在以编程方式构建堆栈框架。但请注意,它依赖于未记录的私有API。

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

        2
  •  7
  •   Dave Stenglein    16 年前

    你通常想要的是延续,我认为这已经是这个问题的一个标签。

    如果你有能力处理系统中的所有代码,你可能想试试 这样做,而不是处理解释器堆栈内部。我不确定这会多么容易坚持下去。

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

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

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

        3
  •  3
  •   Mike McKerns    11 年前

    如果你不介意完全转向另一个python发行版,那么无堆栈python可能是最好的。 stackless 可以序列化 一切 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 注册表,所以如果你有一些使用 泡菜 而且你无法真正编辑它,那么只需导入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
    

    莳萝 也有 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    16 年前

    您可以通过抛出异常并在回溯中后退一帧来获取现有的堆栈帧。问题是没有提供在代码块的中间(frame.f_tlasti)恢复执行的方法。

    可恢复异常是一个非常有趣的语言概念,尽管很难想出一种合理的方式,让它们与Python现有的try/finally和块进行交互。

    目前,正常的做法是简单地使用线程在控制器的单独上下文中运行工作流。(或者协程/greenlets,如果你不介意的话)。

        5
  •  2
  •   ConcernedOfTunbridgeWells    14 年前

    使用标准CPython时,由于堆栈中C和Python数据的混合,这很复杂。重建调用堆栈需要同时重建C堆栈。这真的把它放在了一个太硬的篮子里,因为它可能会将实现与特定版本的CPython紧密结合。

    无堆栈Python允许对小任务进行酸洗,这提供了开箱即用所需的大部分功能。

        6
  •  1
  •   Brad Clements    16 年前

    我也有同样的问题要解决。我想知道最初的海报决定做什么。

    stackless声称,只要没有相关的“阻塞”C堆栈(阻塞是我的措辞选择),它就可以阻塞小任务。

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

        7
  •  1
  •   lost    12 年前

    使用怎么样 joblib ?

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

    推荐文章