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),而在其他情况下,可能需要翻转,或者您可能需要全部三个。算法将很难确定您的偏好

  • 您可以在检测到故障的位置启动pdb,检查您喜欢的任何内容(甚至是调用堆栈),然后恢复“正常”执行。这只在某些情况下可能发生;e、 g.可能不适用于web应用程序

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