204*_*204 3 python unit-testing execution python-3.x
我想根据完成时执行的循环和条件来获取 python 函数的执行跟踪。但是,我想在不使用附加参数检测原始 python 函数的情况下执行此操作。例如:
def foo(a: int, b: int):
while a:
a = do_something()
if b:
a = do_something()
if __name__ == "__main__":
foo(a, b)
Run Code Online (Sandbox Code Playgroud)
在执行之后,foo()我想要一个类似的执行跟踪:
[while: true, if:false, while: true, if: true, while: false, ...]它记录了代码中条件评估的序列。有没有办法为任意的python函数自动获取这些信息?
我了解“覆盖率”python 模块返回“分支覆盖率”信息。但我不确定如何在这种情况下使用它?
您可以将trace_conditions.py用作起点,并在需要时对其进行修改。
foo 问题中定义的函数用于以下示例:
from trace_conditions import trace_conditions
# (1) This will just print conditions
traced_foo = trace_conditions(foo)
traced_foo(a, b)
# while c -> True
# if d -> True
# ...
# (2) This will return conditions
traced_foo = trace_conditions(foo, return_conditions=True)
result, conditions = traced_foo(a, b)
# conditions = [('while', 'c', True), ('if', 'd', True), ...)]
Run Code Online (Sandbox Code Playgroud)
注意:ast.unparse用于获取条件的字符串表示。它是在 Python 3.9 中引入的。如果你想使用Python的旧版本,或许你会想安装第三方软件包astunparse,然后在函数中使用它_condition_to_string。否则trace_conditions不会返回条件的字符串表示。
基本上,我们希望以编程方式将捕获器添加到函数的代码中。例如,print接球手可能如下所示:
while x > 5:
print('while x > 5', x > 5) # <-- print condition after while
# do smth
print('if x > 5', x > 5) # <-- print condition before if
if x > 5:
# do smth
Run Code Online (Sandbox Code Playgroud)
因此,主要思想是在 python 中使用代码自省工具(inspect、ast、exec)。
这里我简单解释一下trace_conditions.py 中的代码:
trace_conditions主要功能不言自明,简单地反映了整个算法:(1)构建句法树;(2) 注入条件捕获器;(3) 编译新函数。
def trace_conditions(
func: Callable, return_conditions=False):
catcher_type = 'yield' if return_conditions else 'print'
tree = _build_syntactic_tree(func)
_inject_catchers(tree, catcher_type)
func = _compile_function(tree, globals_=inspect.stack()[1][0].f_globals)
if return_conditions:
func = _gather_conditions(func)
return func
Run Code Online (Sandbox Code Playgroud)
唯一需要解释的是globals_=inspect.stack()[1][0].f_globals。为了编译我们需要给蟒蛇由该函数使用的所有模块的新功能(例如,它可以使用math,numpy,django等...)。并inspect.stack()[1][0].f_globals简单地获取在调用函数的模块中导入的所有内容。
警告!
# math_pi.py
import math
def get_pi():
return math.pi
# test.py
from math_pi import get_pi
from trace_conditions import trace_conditions
traced = trace_conditions(get_pi)
traced() # Error! Math is not imported in this module
Run Code Online (Sandbox Code Playgroud)
为了解决这个问题,你可以修改代码,trace_conditions.py或只加import math在test.py
在这里,我们首先使用得到的功能的源代码inspect.getsource,然后使用解析它在语法树ast.parse。不幸的是,如果从 调用,python 无法检查函数的源代码decorator,因此使用这种方法似乎无法使用方便的装饰器。
在这个函数中,我们遍历给定的语法树,找到while和if语句,然后在它们之前或之后注入捕获器。ast模块有方法walk,但它只返回节点本身(没有父节点),所以我实现了稍微改变的版本,walk它也返回父节点。如果我们想在之前插入 catcher,我们需要知道 parent if。
def _inject_catchers(tree, catcher_type):
for parent, node in _walk_with_parent(tree):
if isinstance(node, ast.While):
_catch_after_while(node, _create_catcher(node, catcher_type))
elif isinstance(node, ast.If):
_catch_before_if(parent, node, _create_catcher(node, catcher_type))
ast.fix_missing_locations(tree)
Run Code Online (Sandbox Code Playgroud)
最后,我们调用ast.fix_missing_locations有助于正确填写技术领域的函数,例如lineno编译代码所需的其他领域。通常,在修改句法树时需要使用它。
elif语句有趣的是,python 在它的 ast 语法elif语句中没有,所以它只有if-else语句。该ast.If节点具有body包含if主体表达式的字段orelse和包含else块表达式的字段。并且elifcase 仅由字段ast.If内的节点表示orelse。这个事实反映在函数_catch_before_if 中。
_gather_conditions)有几种方法可以捕获条件,最简单的方法就是使用print它,但是如果您想稍后在 Python 代码中处理它们,这种方法将不起作用。一种直接的方法是拥有一个全局空列表,您将在执行函数期间在其中附加条件及其值。但是,我认为此解决方案在命名空间中引入了一个新名称,该名称可能会与函数内的本地名称混淆,因此我决定它应该对yield条件及其信息更安全。
该函数_gather_conditions正在为带有注入yield语句的函数添加一个包装器,它只是收集所有产生的条件并返回函数和条件的结果。
| 归档时间: |
|
| 查看次数: |
200 次 |
| 最近记录: |