Python:执行期间分析复杂语句

3 投票
5 回答
637 浏览
提问于 2025-04-15 16:21

我想知道有没有办法在执行 Python 语句时获取一些关于它的元信息。

假设这是一个复杂的语句,由一些用 连接的单个语句组成(A、B 等是布尔函数)。

if A or B and ((C or D and E) or F) or G and H:

我想知道这个语句的哪一部分导致它的结果为真,这样我就可以利用这个信息。在这个例子中,有三个可能的候选部分:

A
B and ((C or D and E) or F)
G and H

在第二种情况下,我想知道是 (C or D and E) 还是 F 使得结果为真,等等……

有没有什么方法可以不解析这个语句?我能否以某种方式连接到解释器,或者利用 inspect 模块,但我还没有找到合适的方法?我不想调试,我真正想知道的是在运行时,哪个部分的或链触发了这个语句。

编辑 - 进一步信息:我想用这个的应用类型是一个分类算法,它输入一个对象,并根据其属性输出一个特定的类别。我需要知道哪些属性对这个类别是决定性的。
正如你可能猜到的,上面的复杂语句来自于分类算法。这个算法的代码是从一个正式的伪代码生成的,包含大约 3000 个嵌套的 if-elif 语句,以层次化的方式确定类别,像这样:

if obj.attr1 < 23 and (is_something(obj.attr10) or eats_spam_for_breakfast(obj)):
    return 'Category1'
elif obj.attr3 == 'Welcome Home' or count_something(obj) >= 2:
    return 'Category2a'
elif ...

所以除了类别本身,我还需要标记出对该类别决定性的属性,这样如果我删除其他所有属性,这个对象仍然会被分配到同一个类别(因为语句中的 )。这些语句可能非常长,最长可达 1000 个字符,并且嵌套得很深。每个对象最多可以有 200 个属性。

非常感谢你的帮助!

编辑 2:在过去的两周里没找到时间。感谢你提供的这个解决方案,它有效!

5 个回答

0

Python解释器并没有提供一种方法,让你在运行时查看一个表达式是怎么被计算的。虽然sys.settrace()这个函数可以让你注册一个回调函数,这个回调会在每一行代码执行时被调用,但这对于你想做的事情来说,实在是太粗糙了。

不过,我尝试过一个疯狂的黑科技,可以让你在每次执行字节码时都调用这个函数:Python字节码追踪

即便如此,我还是不知道怎么找到执行状态,比如解释器栈上的值。

我觉得要实现你想要的功能,唯一的方法就是对代码进行算法上的修改。你可以选择转换你的源代码(不过你说过不想解析代码),或者转换编译后的字节码。这两者都不是简单的事情,我相信如果你尝试的话,会遇到很多困难。

抱歉让你失望了……

顺便问一下:你打算用这种技术做什么应用呢?

1

据我记得,Python 并不是直接返回 True 或 False:

重要的例外是,布尔运算 orand 总是返回它们的一个操作数。

关于 Python 标准库 - 真值测试
因此,下面的内容是有效的:

A = 1
B = 0
result = B or A # result == 1
3

你能把你最初的代码改成这样吗:

if A or B and ((C or D and E) or F) or G and H:

比如说:

e = Evaluator()
if e('A or B and ((C or D and E) or F) or G and H'):

...?如果可以的话,那就有希望了!Evaluator类在被调用时,会把它的字符串参数编译成代码,然后用一个空的真实字典作为全局变量,再用一个伪字典作为局部变量,这个伪字典实际上会把值的查找委托给调用者的局部和全局变量(这有点小技巧,但其实不难;-)同时,它还会记录下查找过的名字。由于Python的andor有短路行为,你可以从实际查找的名字中推断出哪个名字决定了表达式的真假值。在X or Y or Z中,第一个为真的值(如果有的话)会是最后一个被查找的,而在X and Y and Z中,第一个为假的值会是最后一个被查找的。

这样有帮助吗?如果有,而且你需要编码方面的帮助,我很乐意详细讲解,但首先我想确认一下,得到Evaluator的代码确实能解决你想解决的问题!-)

编辑:这里是实现Evaluator的代码,并展示它的用法:

import inspect
import random

class TracingDict(object):

  def __init__(self, loc, glob):
    self.loc = loc
    self.glob = glob
    self.vars = []

  def __getitem__(self, name):
    try: v = self.loc[name]
    except KeyError: v = self.glob[name]
    self.vars.append((name, v))
    return v


class Evaluator(object):

  def __init__(self):
    f = inspect.currentframe()
    f = inspect.getouterframes(f)[1][0]
    self.d = TracingDict(f.f_locals, f.f_globals)

  def __call__(self, expr):
    return eval(expr, {}, self.d)


def f(A, B, C, D, E):
  e = Evaluator()
  res = e('A or B and ((C or D and E) or F) or G and H')
  print 'R=%r from %s' % (res, e.d.vars)

for x in range(20):
  A, B, C, D, E, F, G, H = [random.randrange(2) for x in range(8)]
  f(A, B, C, D, E)

这是一个示例运行的输出:

R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 0), ('B', 1), ('C', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 0), ('B', 0), ('G', 1), ('H', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 0), ('B', 1), ('C', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 0), ('B', 1), ('C', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=0 from [('A', 0), ('B', 0), ('G', 0)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=1 from [('A', 1)]
R=0 from [('A', 0), ('B', 0), ('G', 0)]
R=1 from [('A', 0), ('B', 1), ('C', 1)]

你可以看到,通常情况下(大约50%的时间)A为真,这会短路后面的所有判断。当A为假时,B会被判断——如果B也为假,那么接下来判断G;如果B为真,那么判断C。

撰写回答