【问题标题】:Python assert -- improved introspection of failure?Python 断言——改进了对失败的自省?
【发布时间】:2010-11-21 11:18:24
【问题描述】:

这是一个相当无用的断言错误;它不会告诉所涉及的表达式的值(假设使用的常量实际上是变量名):

$ python -c "assert 6-(3*2)"
[...]
AssertionError

在 Python 中是否有更好的 assert 实现更花哨?它不能在执行过程中引入额外的开销(除非断言失败)。如果使用-O 标志,则必须关闭。

编辑:我知道断言的第二个参数是一个字符串。我不想写一个 .. 因为它被编码在被断言的表达式中。干燥(不要重复自己)。

【问题讨论】:

    标签: python debugging assert syntactic-sugar


    【解决方案1】:

    将你的函数安装为sys.excepthook——见the docs。你的函数,如果第二个参数是AssertionError,可以自省你的内心;特别是,通过第三个参数,traceback,它可以得到断言失败的帧和确切的位置,通过源或字节码得到失败的异常,所有相关变量的值等。模块inspect帮助。

    完全通用是一项相当大的工作,但是根据您在编写asserts 时愿意接受的限制,可以大大减轻它(例如,将它们限制为仅局部或全局变量比可能涉及闭包的非局部变量更容易自省,等等)。

    【讨论】:

    • 好。现在是否有一个 Python 库用于此.. 还是我必须自己编写? :-)(我可能不会......因为这对我来说是一项低优先级的任务)
    • 不幸的是,我不知道现有的 Python 库可以完成所有这些工作,除了面向测试的库(可能必须进行调整才能在生产代码中使用它们)。
    【解决方案2】:

    您可以将消息附加到assert

    assert 6-(3*2), "always fails"
    

    消息也可以动态构建:

    assert x != 0, "x is not equal to zero (%d)" % x
    

    有关更多信息,请参阅 Python 文档中的 The assert statement

    【讨论】:

    • 当然,我知道这一点。我不想写一个,因为它被编码在被断言的表达式中。干燥。
    • 我明白你的意思。我不相信 Python 有办法做到这一点。
    【解决方案3】:

    @Mark Rushakoff said nose 可以评估失败的断言。它也适用于标准 assert

    # test_error_reporting.py
    def test():
        a,b,c = 6, 2, 3
        assert a - b*c
    

    nosetests'求助:

    $ nosetests --help|grep -B2 assert
      -d, --detailed-errors, --failure-detail
                            Add detail to error output by attempting to evaluate
                            failed asserts [NOSE_DETAILED_ERRORS]
    

    例子:

    $ nosetests -d
    F
    ======================================================================
    FAIL: test_error_reporting.test
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "..snip../site-packages/nose/case.py", line 183, in runTest
        self.test(*self.arg)
      File "..snip../test_error_reporting.py", line 3, in test
        assert a - b*c
    AssertionError:
        6,2,3 = 6, 2, 3
    >>  assert 6 - 2*3
    
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.089s
    
    FAILED (failures=1)
    

    【讨论】:

    • 问题是关于在应用程序代码中使用断言(由用户直接调用,例如 ./foo.py .. 或在 Windows 资源管理器上单击“foo.pyw” ),而不是测试代码 .. 我实际上对 py.test 的断言输出感到满意。
    • @srid:在这种情况下写入:__debug__ and your_fancy_assert(expression) -- '-O' 没有开销。
    • 这听起来很有趣;太糟糕了 Python 没有 macro 功能。
    • @SridharRatnakumar:它有(现在)macropy
    • 为了使自省工作,您还需要省略可选的msg 参数。
    【解决方案4】:

    The nose testing suite applies introspection to asserts

    但是,AFAICT,您必须调用 他们的 断言来进行自省:

    import nose
    def test1():
        nose.tools.assert_equal(6, 5+2)
    

    结果

    C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py F ==================================================== ===================== 失败:test.test1 -------------------------------------------------- -------------------- 回溯(最近一次通话最后): 文件“C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py”,行 183,在运行测试中 self.test(*self.arg) 文件“C:\temp\py\test.py”,第 3 行,在 test1 鼻子.tools.assert_equal(6, 5+2) 断言错误:6!= 7 >> 引发 self.failureException,\ (无或 '%r != %r' % (6, 7))

    注意那里的 AssertionError。当我的线路只是assert 6 == 5+2 时,我会得到:

    C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py F ==================================================== ===================== 失败:test.test1 -------------------------------------------------- -------------------- 回溯(最近一次通话最后): 文件“C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py”,行 183,在运行测试中 self.test(*self.arg) 文件“C:\temp\py\test.py”,第 2 行,在 test1 断言 6 == 5 + 2 断言错误: >> 断言 6 == 5 + 2

    另外,我不确定他们的断言是否会被-O 跳过,但这将是一个非常快速的检查。

    【讨论】:

    【解决方案5】:

    我为sys.excepthook 编写了一个替代代码(对任何未处理的异常都调用它),它比标准的要花哨一些。它将分析发生异常的行并打印该行中引用的所有变量(它不会打印所有局部变量,因为这可能会产生太多噪音——而且,重要的变量可能是全局变量)。

    我称它为 py_better_exchook(完美名称),它是 here

    示例文件:

    a = 6
    
    def test():
        unrelated_var = 43
        b,c = 2, 3
        assert a - b*c
    
    import better_exchook
    better_exchook.install()
    
    test()
    

    输出:

    $ python test_error_reporting.py 
    EXCEPTION
    Traceback (most recent call last):
      File "test_error_reporting.py", line 12, in <module>
        line: test()
        locals:
          test = <local> <function test at 0x7fd91b1a05f0>
      File "test_error_reporting.py", line 7, in test
        line: assert a - b*c
        locals:
          a = <global> 6
          b = <local> 2
          c = <local> 3
    AssertionError
    

    还有其他几种选择:

    【讨论】:

    • 这个可以和import traceback; traceback.print_stack()一样使用,即打印然后继续执行? (我尝试查看代码,但它超出了我的能力。)非常感谢,很高兴发现这一点,多年来一直希望获得更好的异常报告!
    • @Chris 是的,当然。您只需在try: ... except Exception: ... 中捕获异常,然后打印带有sys.excepthook(*sys.exc_info()) 的所有信息的异常,然后继续。
    • 谢谢!是否人为地引发异常,例如 try:raise Exception();except Exception:sys.excepthook(*sys.exc_info()) 是最好的方法,即使没有异常,我只是在工作代码中的某个点寻找状态转储?
    • 如果你没有异常,你不需要提出一个虚拟的。你可以直接调用better_exchook.print_tb(None),它会打印当前的traceback。
    • 顺便说一句,打印堆栈跟踪/异常通常对意外异常很有帮助。对于调试,如果可能,通常最好在 IDE 中使用真正的调试器。例如。我可以推荐 PyCharm(免费的社区版就好了)。您只需在任何您想要的地方放置一个断点,然后您就可以获得您想要的所有信息。
    【解决方案6】:

    在您的断言中添加一条消息,如果断言失败将显示该消息:

    $ python -c "assert 6-(3*2), '6-(3*2)'"
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    AssertionError: 6-(3*2)
    

    我能想到的自动提供此功能的唯一方法是在过程调用中包含断言,然后检查堆栈以获取该行的源代码。不幸的是,额外的调用会在测试中引入开销,并且不会被 -O 禁用。

    【讨论】:

    • 没错。在 -O 上禁用这种“自省”是问题的关键。
    • .. 但是,如果仅在断言错误(而非断言调用)期间调用此函数,则这不是开销。
    【解决方案7】:

    听起来您真正想要做的是在assert 之前设置一个调试器断点,并从您最喜欢的调试器中尽可能多地进行检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      • 2011-05-27
      • 1970-01-01
      • 2023-02-22
      相关资源
      最近更新 更多