Python代码:关于循环/条件执行跟踪的信息
我想根据完成时执行的循环和条件来获取python函数的执行跟踪。但是,我希望在不使用附加参数插入原始python函数的情况下执行此操作。例如:Python代码:关于循环/条件执行跟踪的信息,python,python-3.x,unit-testing,execution,Python,Python 3.x,Unit Testing,Execution,我想根据完成时执行的循环和条件来获取python函数的执行跟踪。但是,我希望在不使用附加参数插入原始python函数的情况下执行此操作。例如: def foo(a: int, b: int): while a: a = do_something() if b: a = do_something() if __name__ == "__main__": foo(a, b) 执行foo()之后,我需要一
def foo(a: int, b: int):
while a:
a = do_something()
if b:
a = do_something()
if __name__ == "__main__":
foo(a, b)
执行foo()
之后,我需要一个执行跟踪,如下所示:
[while:true,if:false,while:true,if:true,while:false,…]
用于记录代码中的条件求值序列。有没有办法为任意python函数自动获取此信息
我知道“覆盖率”python模块返回“分支覆盖率”信息。但我不确定在这种情况下如何使用它?您可以将其用作起点,并在需要时对其进行修改 例子
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), ...)]
注意:ast.unpasse
用于获取条件的字符串表示形式。它是在Python3.9中引入的。如果您想使用较旧版本的Python,可能需要安装第三方软件包,然后在函数中使用它。否则,trace_conditions
将不会返回条件的字符串表示形式
TL;博士
主意
基本上,我们希望通过编程方式将捕捉器添加到函数的代码中。例如,print
catchers可以如下所示:
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
唯一需要解释的是globals\uu=inspect.stack()[1][0].f\u globals
。为了编译一个新函数,我们需要为python提供该函数使用的所有模块(例如,它可能使用math
,numpy
,django
,等等)。和inspect.stack()[1][0].f_globals
只需获取调用函数模块中导入的所有内容
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
警告强>
# 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
要解决此问题,您可以在trace\u conditions.py
中修改代码,也可以在test.py
中添加import math
_构建语法树
在这里,我们首先获取函数using的源代码,然后使用语法树解析它。不幸的是,如果函数的源代码是从decorator
调用的,python就无法检查它,因此使用这种方法似乎不可能使用方便的decorator
_注射捕集器
在这个函数中,我们遍历给定的语法树,找到while
和if
语句,然后在它们之前或之后插入捕捉器ast
模块有方法,但它只返回节点本身(没有父节点),所以我实现了稍微更改的walk
版本,它也返回父节点。如果要在if
之前插入catcher,我们需要知道parent
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)
最后,我们调用ast.fix_missing_locations
函数,该函数有助于正确填写技术字段,如lineno
以及编译代码所需的其他字段。通常,在修改语法树时需要使用它
捕捉elif
语句
有趣的是python的ast语法中没有elif
语句,所以它只有if-else
语句。ast.If
节点具有包含If
主体表达式的字段body
,以及包含else
块表达式的字段orelse
。而elif
case则简单地由ast.If
节点表示,节点位于orelse
字段内。这一事实反映在功能上
捕捉器(和\u聚集\u条件
)
有几种方法可以捕获条件,最简单的方法是只打印它,但是如果您想稍后在python代码中处理它们,这种方法将不起作用。一种简单的方法是创建一个全局空列表,在函数执行期间,您将在其中附加条件及其值。但是,我认为该解决方案在名称空间中引入了一个新名称,它可能会与函数中的本地名称混淆,因此我决定,产生
条件及其信息应该更安全
函数\u collect\u conditions
添加了一个带有注入的yield
语句的包装函数,它只收集所有生成的条件并返回函数和条件的结果。,我想在不使用附加参数插入原始python函数的情况下执行此操作,您能详细说明一下吗?因为任何类型的跟踪都需要检测。不一定要更改函数本身的源代码,但一定要使用Python内置的跟踪支持。其他跟踪支持也不错:)您的分支跟踪需求不是很清楚,或者我怀疑您没有完全理解其中的复杂性。例如,应如何记录复合条件?例如:如果不是(a或b):
。那elif
和else
块呢?条件表达式也是您需要跟踪的分支决策吗?所有这些问题都使我们很难写出一个有针对性的答案。可能是使用sys.set_trace()
和跟踪操作码就足够了,但即使如此,理解如果和while如何导致特定的跳转组合也不是一件小事。因此,我最多只能指向和的文档。