python:自动打印表达式中每个组件的表示形式
我发现自己在写这样的断言:python:自动打印表达式中每个组件的表示形式,python,parsing,exception,Python,Parsing,Exception,我发现自己在写这样的断言: if f(x, y) != z: print(repr(x)) print(repr(y)) print(repr(z)) raise MyException('Expected: f(x, y) == z') 我想知道是否有一种方法可以编写一个函数,该函数接受一个有效的Python表达式和一个异常类作为输入,对该表达式求值,如果它发现该表达式为false,则打印出表达式中每个最低级别项的表示,并引发给定的异常 # validate is the m
if f(x, y) != z:
print(repr(x))
print(repr(y))
print(repr(z))
raise MyException('Expected: f(x, y) == z')
我想知道是否有一种方法可以编写一个函数,该函数接受一个有效的Python表达式和一个异常类作为输入,对该表达式求值,如果它发现该表达式为false,则打印出表达式中每个最低级别项的表示,并引发给定的异常
# validate is the mystery function
validate('f(x, y) == z', MyException)
这是可能的。您可以使用Python编译器(不同版本的详细信息不同,因此这只是一个概述)将给定的表达式编译成AST。然后,您可以将AST编译成一个代码对象并对其求值(或者首先调用
eval
,不管怎样)。然后,如果值为falsy,则检查AST以查看表达式是如何构造的。打印出表达式中根据AST按名称访问的每个项的值。断言如何
assert f(x, y) != z, 'Expected: f(%r, %r) == %r'%(x,y,z)
编辑
要将%r添加到print repr中-感谢您的评论。可能有一种方法可以根据您的要求修改某些内容,但替代方法至少要简单得多:
if f(x, y) != z:
raise MyException("Unexpected: the foobar is glowing; " +
"f(%r, %r) != %r" % (x, y, z))
在这里,您似乎需要x和y,但不需要(调用的结果)f(x,y),而在其他情况下,可能需要翻转,或者您可能需要全部三个。算法将很难确定您的偏好if f(x, y) != z:
import pdb
pdb.set_trace()
raise MyException("Unexpected: ...")
nose
测试运行程序有一个名为“failure detail”的插件,它正好提供以下服务:。这个解决方案比您要求的要好,因为表达式不是字符串,它是一个实际的断言,稍后将进行反思以找到要分析的源代码
例如:
换句话说,如果你有一个测试
比如:
您将获得如下输出:
你可以反过来做:
expr = 'f(%r, %r) != %r' % (x,y,z)
if eval(expr):
raise MyException(expr)
换句话说:
def validate(expr,myexception):
if eval(expr):
raise myexception(expr)
但是相当脏:)下面是一个实现:
import inspect, keyword, pprint, sys, tokenize
def value_in_frame(name, frame):
try:
return frame.f_locals[name]
except KeyError:
try:
return frame.f_globals[name]
except KeyError:
raise ValueError("Couldn't find value for %s" % name)
def validate(expr, exc_class=AssertionError):
"""Evaluate `expr` in the caller's frame, raise `exc_class` if false."""
frame = inspect.stack()[1][0]
val = eval(expr, frame.f_globals, frame.f_locals)
if not val:
rl = iter([expr]).next
for typ, tok, _, _, _ in tokenize.generate_tokens(rl):
if typ == tokenize.NAME and not keyword.iskeyword(tok):
try:
val = value_in_frame(tok, frame)
except ValueError:
val = '???'
else:
val = repr(val)
print " %s: %s" % (tok, val)
raise exc_class("Failed to validate: %s" % expr)
if __name__ == '__main__':
a = b = 3
validate("a + b == 5")
您不需要访问调用方的作用域吗?(可能与
检查或其他东西有关,但不是很好或可靠)。酷。我想它在编译器和编译器版本之间是不可移植的?也许有一个(特定于编译器的)模块允许通过AST进行迭代?@delnan:是的,您可能需要将globals()
和locals()
传递给validate()
函数。甚至更重要的是,对于嵌套函数,闭包,等等。您可以使用模块inspect
中的堆栈函数进行检查。这会很复杂,但我想如果你检查AST并在其中找到一个名为“a”的var,你可以向后看直到找到它。我想说这是绝对可行的,只是需要一些工作。你想要%r
,但除此之外:是的,你是对的。刚刚修改过。谢谢你的提示。@msavadores:哦,这会有用的,但我的意思是每次表达都会不同。它可能是f(x,y)!=z
,下次它可能是x
,另一次x不是空的
。我希望调用一个函数并将表达式作为字符串传递给它,而不是每次稍加修改就写这一行。@max:这样可以节省大量的键入工作量(您还是会以某种方式生成字符串,对吗?)?(通过半个小时的讨论,足以证明我们发现的头痛问题,以及在实施此操作时您将遇到的问题?)。很可能现在。@dorkitude:在%-格式中,%s
表示对应参数的“str()
”,而%r
表示对应参数的“repr()
”(这是OP要求的)。%r而不是%s,并且对于许多类型,它都会因语法错误而失败。很脏,;不推荐。我会翻转异常消息:不要说你想要什么,而是指定你检测到的错误。这将给出与您测试的表达式相同的表达式(!=),而不是相反的(=)。酷。这是您建议使用的方法,还是您仍然建议使用更传统的方法?
def validate(expr,myexception):
if eval(expr):
raise myexception(expr)
import inspect, keyword, pprint, sys, tokenize
def value_in_frame(name, frame):
try:
return frame.f_locals[name]
except KeyError:
try:
return frame.f_globals[name]
except KeyError:
raise ValueError("Couldn't find value for %s" % name)
def validate(expr, exc_class=AssertionError):
"""Evaluate `expr` in the caller's frame, raise `exc_class` if false."""
frame = inspect.stack()[1][0]
val = eval(expr, frame.f_globals, frame.f_locals)
if not val:
rl = iter([expr]).next
for typ, tok, _, _, _ in tokenize.generate_tokens(rl):
if typ == tokenize.NAME and not keyword.iskeyword(tok):
try:
val = value_in_frame(tok, frame)
except ValueError:
val = '???'
else:
val = repr(val)
print " %s: %s" % (tok, val)
raise exc_class("Failed to validate: %s" % expr)
if __name__ == '__main__':
a = b = 3
validate("a + b == 5")