有什么方法可以在 CPython 中创建函数时执行代码?
Is there any way to execute code on function creation in CPython?
有什么方法可以挂钩 CPython 解释器,以便每个函数创建(def
、lambda
)都会调用我定义的过程?不幸的是,sys.settrace
和 sys.setprofile
似乎没有涵盖 def
和 lambda
。
更新:
似乎Python 3.7 f_trace_opcodes
...是否有针对早期版本的选项?
在 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)
但是如果你不能......你 可以 做一些更复杂的事情,这取决于你想要这个的原因,但是 none其中很容易:
- 使用
line
跟踪,检查代码直到下一行。这对于 def
来说是微不足道的,但是对于 lambda
(和理解 1)来说这将是一个很大的痛苦,因为 lambda
(甚至其中五个)可以出现在语句的中间。您可以 ast.parse
源代码或检查字节码,以确定其中定义了函数,但仍然无法在定义时调用您的挂钩。
- 不使用跟踪,而是编写一个导入挂钩,在导入时修改代码。执行此操作的简单方法可能是在 AST 级别:解析源代码后,在每个
MAKE_FUNCTION
.3[= 之前或之后使用 NodeTransformer
to inject calls to some function2 before or after each def
and lambda
node, then compile the transformed tree. But you could also do it at the bytecode level with bytecode
or byteplay
61=]
- 脚本
pdb
而不是编写您自己的调试器。我不确定这是否有帮助,因为 pdb
首先无法逐步执行表达式的一部分。
- 调试 CPython 本身,并在调用您的代码的
ceval
循环中的 MAKE_FUNCTION
处理程序中添加一个断点。当然,您的代码在调试器的解释器中 — 对于 gdb
和 lldb
,它可以是 Python,但它仍然不是 相同的 Python 你正在调试的解释器。而且,虽然可以在调试的解释器中递归评估代码(或触发它的 pdb
),但这并不容易,而且在解决它的过程中到处都是段错误。
1.理解(列表理解除外,在2.x中)是通过定义然后调用函数来实现的。因此,任何依赖 MAKE_FUNCTION
操作码或类似操作码的方法也会触发理解,而那些依赖源或 AST 解析的方法则不会(当然,除非你明确地这样做)。
2。显然,您还需要在每个模块的顶部注入一个 import
以使该功能可用,或者将该功能注入内置模块。
3。而MAKE_CLOSURE
,对于早期版本的Python.
有什么方法可以挂钩 CPython 解释器,以便每个函数创建(def
、lambda
)都会调用我定义的过程?不幸的是,sys.settrace
和 sys.setprofile
似乎没有涵盖 def
和 lambda
。
更新:
似乎Python 3.7 f_trace_opcodes
...是否有针对早期版本的选项?
在 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)
但是如果你不能......你 可以 做一些更复杂的事情,这取决于你想要这个的原因,但是 none其中很容易:
- 使用
line
跟踪,检查代码直到下一行。这对于def
来说是微不足道的,但是对于lambda
(和理解 1)来说这将是一个很大的痛苦,因为lambda
(甚至其中五个)可以出现在语句的中间。您可以ast.parse
源代码或检查字节码,以确定其中定义了函数,但仍然无法在定义时调用您的挂钩。 - 不使用跟踪,而是编写一个导入挂钩,在导入时修改代码。执行此操作的简单方法可能是在 AST 级别:解析源代码后,在每个
MAKE_FUNCTION
.3[= 之前或之后使用NodeTransformer
to inject calls to some function2 before or after eachdef
andlambda
node, then compile the transformed tree. But you could also do it at the bytecode level withbytecode
orbyteplay
61=] - 脚本
pdb
而不是编写您自己的调试器。我不确定这是否有帮助,因为pdb
首先无法逐步执行表达式的一部分。 - 调试 CPython 本身,并在调用您的代码的
ceval
循环中的MAKE_FUNCTION
处理程序中添加一个断点。当然,您的代码在调试器的解释器中 — 对于gdb
和lldb
,它可以是 Python,但它仍然不是 相同的 Python 你正在调试的解释器。而且,虽然可以在调试的解释器中递归评估代码(或触发它的pdb
),但这并不容易,而且在解决它的过程中到处都是段错误。
1.理解(列表理解除外,在2.x中)是通过定义然后调用函数来实现的。因此,任何依赖 MAKE_FUNCTION
操作码或类似操作码的方法也会触发理解,而那些依赖源或 AST 解析的方法则不会(当然,除非你明确地这样做)。
2。显然,您还需要在每个模块的顶部注入一个 import
以使该功能可用,或者将该功能注入内置模块。
3。而MAKE_CLOSURE
,对于早期版本的Python.