Python &引用;“在线”;函数的猴子补丁

Python &引用;“在线”;函数的猴子补丁,python,function,monkeypatching,Python,Function,Monkeypatching,您的程序刚刚在pdb上暂停。set_trace() 是否有办法对当前正在运行的功能进行修补并“恢复”执行? 这可以通过调用帧操作实现吗 一些背景: 通常,我会有一个处理大量数据的复杂函数,而不知道我会找到什么样的数据: def process_a_lot(data_stream): #process a lot of stuff #... data_unit= data_stream.next() if not can_process(data_unit)

您的程序刚刚在
pdb上暂停。set_trace()

是否有办法对当前正在运行的功能进行修补并“恢复”执行?

这可以通过调用帧操作实现吗


一些背景:

通常,我会有一个处理大量数据的复杂函数,而不知道我会找到什么样的数据:

def process_a_lot(data_stream):
    #process a lot of stuff
    #...
    data_unit= data_stream.next()
    if not can_process(data_unit)
        import pdb; pdb.set_trace()
    #continue processing
这种方便的构造在遇到未知数据时启动一个交互式调试器,因此我可以随意检查它并更改
process\u lot
代码以正确处理它

这里的问题是,当
data\u-stream
很大时,您不想再次仔细阅读所有数据(假设
next
速度很慢,因此您无法保存已有数据并在下次运行时跳过)

当然,您可以在调试器中随时替换其他函数。您也可以替换函数本身,但它不会更改当前的执行上下文

编辑: 因为有些人被跟踪了:
我知道有很多方法可以构造代码,使处理功能与
process\u a\u lot
分离。我不是在问如何构造代码,而是在代码不准备处理替换时如何恢复(在运行时)。

如果我理解正确:

  • 你不想重复所有已经完成的工作

  • 一旦您了解了如何处理新数据,您需要一种方法来用新代码替换
    #继续正常处理

@user2357112的思路是正确的:
预期的_类型
应该是

data_type:(detect_function, handler_function)
detect\u type
需要通过它来找到匹配项。如果没有找到匹配项,
pdb
弹出,然后您可以了解发生了什么,编写一个新的
detect\u函数
handler\u函数
,将它们添加到
预期的\u类型
,并从pdb继续执行

我想知道的是,是否有一种方法可以对当前正在运行的函数(进程)进行修补,并“恢复”执行

因此,您想从pdb内部编写一个新的
处理批
函数,然后在
pdb
调用的位置将控制权转移给它

或者,是否要在pdb外部重写函数,然后以某种方式从
.py
文件重新加载该函数,并将控制权转移到
pdb
调用位置的函数中间

我能想到的唯一可能性是:从
pdb
中导入新编写的函数,然后用新函数中的字节码替换当前的
process\u lot
字节码(我认为它是
func.co\u code
之类的)。确保在
pdb
行之前的新函数(甚至是
pdb
行)中没有任何更改,它可能会工作

但即使是这样,我也会认为这是一个非常脆弱的解决方案。

首先是一个(原型)解决方案,然后是一些重要的警告

# process.py

import sys
import pdb
import handlers

def process_unit(data_unit):
    global handlers
    while True:
        try:
            data_type = type(data_unit)
            handler = handlers.handler[data_type]
            handler(data_unit)
            return
        except KeyError:
            print "UNUSUAL DATA: {0!r}". format(data_unit)
            print "\n--- INVOKING DEBUGGER ---\n"
            pdb.set_trace()
            print
            print "--- RETURNING FROM DEBUGGER ---\n"
            del sys.modules['handlers']
            import handlers
            print "retrying"


process_unit("this")
process_unit(100)
process_unit(1.04)
process_unit(200)
process_unit(1.05)
process_unit(300)
process_unit(4+3j)

sys.exit(0)
以及:

在Python2.7中,这为您提供了一个字典,将预期/已知类型链接到处理每种类型的函数。如果某个类型没有可用的处理程序,则用户将自己放入调试器中,让他们有机会使用适当的处理程序修改
handlers.py
文件。在上面的示例中,没有
浮点值
复杂值的处理程序。当它们出现时,用户必须添加适当的处理程序。例如,可以添加:

def handle_float(x):
    print "FIXED FLOAT {0!r}".format(x)

handler[float] = handle_float
然后:

def handle_complex(x):
    print "FIXED COMPLEX {0!r}".format(x)

handler[complex] = handle_complex
下面是跑步的样子:

$ python process.py
handle_default: 'this'
handle_default: 100
UNUSUAL DATA: 1.04

--- INVOKING DEBUGGER ---

> /Users/jeunice/pytest/testing/sfix/process.py(18)process_unit()
-> print
(Pdb) continue

--- RETURNING FROM DEBUGGER ---

retrying
FIXED FLOAT 1.04
handle_default: 200
FIXED FLOAT 1.05
handle_default: 300
UNUSUAL DATA: (4+3j)

--- INVOKING DEBUGGER ---

> /Users/jeunice/pytest/testing/sfix/process.py(18)process_unit()
-> print
(Pdb) continue

--- RETURNING FROM DEBUGGER ---

retrying
FIXED COMPLEX (4+3j)
好的,这基本上是可行的。您可以将其改进并调整为更适合生产的形式,使其在Python2和Python3之间兼容,等等

在这样做之前,请仔细考虑。

这种“实时修改代码”的方法是一种非常脆弱的模式和容易出错的方法。它鼓励您在关键时刻进行实时热修复。这些修复可能没有良好或充分的测试。几乎从定义上讲,您刚刚发现您正在处理一种新类型的T。您还不太了解T,它为什么会出现,它的边缘情况和故障模式可能是什么,等等。如果您的“修复”代码或热修补程序不起作用,那会怎么样?当然,您可以进行更多的异常处理,捕获更多的异常类,并可能继续

像这样的Web框架具有基本上以这种方式工作的调试模式。但这些都是调试模式,通常不适合生产。此外,如果在调试器中键入错误的命令,该怎么办?不小心键入“退出”而不是“继续”,整个程序就会结束,随之,您希望继续处理。如果这是用于调试(可能是探索新类型的数据流),请使用

如果这是用于生产使用,而是考虑异步、带外检查和校正,而不是将开发人员/操作员置于实时处理流程的中间。

< P> <强> No.<强>

您不能将当前正在运行的Python函数进行moneky修补,然后继续按下去,就好像什么都没发生过一样。至少不是以任何一般或实际的方式

从理论上讲,这是可能的——但只有在有限的条件下,需要付出很大的努力和高超的技巧。这不能以任何概括性来完成

要进行尝试,您必须:

  • 找到相关函数源并编辑它(简单)
  • 将更改后的函数源编译为字节码(直截了当)
  • 插入新字节码代替旧字节码(可行)
  • 将函数内务管理数据更改为指向程序中的“逻辑”“相同点”,其中
    $ python process.py
    handle_default: 'this'
    handle_default: 100
    UNUSUAL DATA: 1.04
    
    --- INVOKING DEBUGGER ---
    
    > /Users/jeunice/pytest/testing/sfix/process.py(18)process_unit()
    -> print
    (Pdb) continue
    
    --- RETURNING FROM DEBUGGER ---
    
    retrying
    FIXED FLOAT 1.04
    handle_default: 200
    FIXED FLOAT 1.05
    handle_default: 300
    UNUSUAL DATA: (4+3j)
    
    --- INVOKING DEBUGGER ---
    
    > /Users/jeunice/pytest/testing/sfix/process.py(18)process_unit()
    -> print
    (Pdb) continue
    
    --- RETURNING FROM DEBUGGER ---
    
    retrying
    FIXED COMPLEX (4+3j)
    
    def a(x):             LOAD_FAST 0 (x)
        y = x + 1         LOAD_CONST 1 (1)
        return y          BINARY_ADD
                          STORE_FAST 1 (y)
                          LOAD_FAST 1 (y)
                          RETURN_VALUE
    ------------------    ------------------
    def a2(x):            LOAD_CONST 1 (2)
        inc = 2           STORE_FAST 1 (inc)
        y = x + inc       LOAD_FAST 0 (x)
        return y          LOAD_FAST 1 (inc)
                          BINARY_ADD
                          STORE_FAST 2 (y)
                          LOAD_FAST 2 (y)
                          RETURN_VALUE
    
    some = SomeObject()
    # blah blah including last touch of `some`
    # ...
    pdb.set_trace()
    # Look, Ma! I'm monkey-patching!
    if some.some_property:
       # oops, `some` was GC'd - DIE DIE DIE