有什么方法可以在CPython中创建函数时执行代码?

时间:2018-09-08 19:04:49

标签: python hook cpython tracing

是否有任何钩挂CPython解释器的方法,以便每次函数创建(deflambda)都会导致对我定义的过程的调用?不幸的是,sys.settracesys.setprofile并没有同时涵盖deflambda

更新:

Python 3.7似乎有f_trace_opcodes ...较早版本是否有任何选择?

1 个答案:

答案 0 :(得分:4)

在3.7之前的版本中,没有等效于opcode的跟踪。如果有的话,该功能将不会首先添加到3.7。

如果您可以升级到3.7,那么您想要的很容易:

def tracefunc(frame, event, arg):
    if event == 'call':
        frame.f_trace_opcodes = True
    elif event == 'opcode':
        if frame.f_code.co_code[frame.f_lasti] == dis.opmap['MAKE_FUNCTION']:
            makefunctiontracefunc(frame)
    return tracefunc
sys.settrace(tracefunc)

但是,如果您做不到……,您可以做很多更复杂的事情,具体取决于您想要这样做的原因,但是没有一件事情很容易做到的:

  • 使用line跟踪,并检查代码,直到下一行为止。对于def来说,这是微不足道的,但是对于lambda(和理解力 1 )来说,这将是一个巨大的痛苦,因为lambda(甚至其中的五个) )可以出现在语句的中间。您可以ast.parse的源代码,也可以检查字节码,以了解其中定义了函数,但是在定义时仍然无法正确调用钩子。
  • 编写导入钩子而不是使用跟踪,该钩子在导入代码时对其进行修改。执行此操作的简单方法可能是在AST级别:解析源之后,使用NodeTransformer在每个def之前或之后使用对某些函数 2 的调用, lambda节点,然后编译转换后的树。但您也可以在每个MAKE_FUNCTION之前或之后,在bytecodebyteplay的字节码级别上执行此操作。 3
  • 脚本pdb而不是编写自己的调试器。我不确定这是否还会有所帮助,因为pdb首先没有办法单步执行表达式的一部分。
  • 调试CPython本身,并在调用代码的MAKE_FUNCTION循环的ceval处理程序中添加一个断点。当然,您的代码在调试器的解释器中-可以是用于gdblldb的Python,但仍不是您要调试的相同 Python解释器。而且,尽管可以将代码递归地评估到调试的解释器中(或触发其pdb),但这并不容易,而且在执行代码时会在各处进行段隔离。

1。理解(2.x中的列表理解除外)是通过定义然后调用一个函数来实现的。因此,任何依赖MAKE_FUNCTION操作码或类似方法的方法也将在理解上触发,而那些依赖源或AST解析的方法则不会(当然,除非您明确地这样做)。

2。显然,您还需要在每个模块的顶部插入一个import才能使该功能可用,或者将该功能插入到内置模块中。

3。还有MAKE_CLOSURE,用于Python的早期版本。