python:自动打印表达式中每个组件的表示

时间:2011-01-23 23:02:18

标签: 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表达式和一个异常类作为输入,计算表达式,如果它发现它是假的,打印出每个的表示形式表达式中最低级别的术语并引发给定的异常?

# validate is the mystery function
validate('f(x, y) == z', MyException)

6 个答案:

答案 0 :(得分:2)

断言怎么样?

assert  f(x, y) != z, 'Expected: f(%r, %r) == %r'%(x,y,z)

修改

将%r添加到print repr中 - 感谢您的评论。

答案 1 :(得分:2)

这是一个实现:

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")

答案 2 :(得分:1)

这是可能的。您可以使用Python编译器(版本之间的细节不同,因此这只是一个概述)将给定的表达式编译为AST。然后你可以将AST编译成代码对象并对其进行评估(或者首先调用eval,无论如何)。然后,如果值为falsy,请检查AST以查看表达式的构造方式。根据AST打印出按名称访问的表达式中每个项目的值。

答案 3 :(得分:1)

可能有办法破解某些事情来做你所要求的事情,但替代方案至少要容易得多:

  1. 如您所知,手动扩展感兴趣的值。这可能更可靠,特别是对于复杂的表达式,因为你更容易说出有趣的东西而不是计算机来推断它。特别是,您可能希望包含一个甚至不在表达式中的值。

    if f(x, y) != z:
      raise MyException("Unexpected: the foobar is glowing; " +
                        "f(%r, %r) != %r" % (x, y, z))
    

    在这里,你似乎想要x和y而不是(调用的结果)f(x,y),而在其他情况下可能会被翻转,或者你可能想要全部三个。算法很难确定您的偏好。

  2. 您可以在检测到故障的位置启动pdb,检查您喜欢的内容(甚至调用堆栈),然后恢复“正常”执行。这只有在某些情况下才有可能;例如可能不适用于网络应用。

    if f(x, y) != z:
      import pdb
      pdb.set_trace()
      raise MyException("Unexpected: ...")
    
  3. 日志记录模块而不是print语句或异常消息。

答案 4 :(得分:1)

nose测试运行器有一个名为“failure detail”的插件,它正好提供了这项服务:http://somethingaboutorange.com/mrl/projects/nose/0.11.1/plugins/failuredetail.html。这个解决方案比你要求的更好,因为表达式不是一个字符串,它是一个实际的断言,稍后会被内省以找到要分析的源代码。

一个例子是older docs

  

换句话说,如果你有一个测试   像:

def test_integers():
    a = 2
    assert a == 4, "assert 2 is 4"
  

您将获得如下输出:

File "/path/to/file.py", line XX, in test_integers:
      assert a == 4, "assert 2 is 4" 
AssertionError: assert 2 is 4
  >>  assert 2 == 4, "assert 2 is 4"

答案 5 :(得分:1)

你可以反过来这样做:

expr = 'f(%r, %r) != %r' % (x,y,z)

if eval(expr):
    raise MyException(expr)

或换句话说:

def validate(expr,myexception):
    if eval(expr):
        raise myexception(expr)

虽然相当脏:)