96

I want to handle AssertionErrors both to hide unnecessary parts of the stack trace from the user and to print a message as to why the error occurred and what the user should do about it.

Is there any way to find out on which line or statement the assert failed within the except block?

try:
    assert True
    assert 7 == 7
    assert 1 == 2
    # many more statements like this
except AssertionError:
    print 'Houston, we have a problem.'
    print
    print 'An error occurred on line ???? in statement ???'
    exit(1)

I don't want to have to add this to every assert statement:

assert 7 == 7, "7 == 7"

because it repeats information.

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
devtk
  • 1,999
  • 2
  • 18
  • 24
  • 11
    Two issues. First, if you are having trouble identifying where the exception is happening in your `try..except`, that's a sign your `try..except` block is too big. Second, the kind of thing meant to be caught by `assert` isn't something the user should ever see. If they see an `AssertionError`, the proper course of action is for them to contact the programmer and say "WTF?!". – John Y Jul 20 '12 at 21:51
  • 3
    @John Y, you seem confused. You're saying `AssertionError`s shouldn't be seen by the user, and then what the user should do when he sees one. It can't be both! – devtk Jul 20 '12 at 22:18
  • 9
    BTW: Asserts should be about the structure of your code, that is, an assert should fail only if you have a bug in your software. They should not be used to check user input. You might consider using a different exception for this application. – Ned Batchelder Jul 21 '12 at 00:42
  • 1
    @NedBatchelder Thanks, you are correct. I may reconsider using asserts here, but the procedure for getting the line code will still be useful. – devtk Jul 21 '12 at 00:54
  • @NedBatchelder Why should asserts not be used for user input validation? What alternatives are recommended? – Konrad Jul 19 '21 at 21:38

2 Answers2

97

Use the traceback module:

import sys
import traceback

try:
    assert True
    assert 7 == 7
    assert 1 == 2
    # many more statements like this
except AssertionError:
    _, _, tb = sys.exc_info()
    traceback.print_tb(tb) # Fixed format
    tb_info = traceback.extract_tb(tb)
    filename, line, func, text = tb_info[-1]

    print('An error occurred on line {} in statement {}'.format(line, text))
    exit(1)
Quentin Pradet
  • 4,691
  • 2
  • 29
  • 41
phihag
  • 278,196
  • 72
  • 453
  • 469
  • How do I get the actual value instead of the line vars? e.g assert a == b I want to show the values of a and b. I tried sys.exc_info()[1] but it's empty – 0x2bad Jun 30 '19 at 19:46
  • 1
    @0x2bad Try `assert a == b, (a, b)`, which will give you the values in `sys.exc_info()[1].args[0]` – or easier, name the AssertionError you catch `ae`, then it's in `ae.args[0]`. You can also create a helper method which does the actual assertion. This is what [`unittest.TestCase.assertEqual`](https://docs.python.org/dev/library/unittest.html#unittest.TestCase.assertEqual) does. – phihag Jul 01 '19 at 07:08
  • 1
    I took the nose approach here https://github.com/nose-devs/nose/blob/7c26ad1e6b7d308cafa328ad34736d34028c122a/nose/inspector.py#L18 to get the exact value. So I can keep the exception message meaning full – 0x2bad Jul 07 '19 at 17:31
48

The traceback module and sys.exc_info are overkill for tracking down the source of an exception. That's all in the default traceback. So instead of calling exit(1) just re-raise:

try:
    assert "birthday cake" == "ice cream cake", "Should've asked for pie"
except AssertionError:
    print 'Houston, we have a problem.'
    raise

Which gives the following output that includes the offending statement and line number:

Houston, we have a problem.
Traceback (most recent call last):
  File "/tmp/poop.py", line 2, in <module>
    assert "birthday cake" == "ice cream cake", "Should've asked for pie"
AssertionError: Should've asked for pie

Similarly the logging module makes it easy to log a traceback for any exception (including those which are caught and never re-raised):

import logging

try:
    assert False == True 
except AssertionError:
    logging.error("Nothing is real but I can't quit...", exc_info=True)
notpeter
  • 1,046
  • 11
  • 16