PYTHON / ERRORS AND EXCEPTIONS
Syntax errors versus runtime exceptions
Tell apart errors that stop a file from running at all and exceptions raised while it runs, and know which ones you can catch.
What you will learn
- Explain why a SyntaxError anywhere in a file prevents line 1 from running
- Classify a failure as parse-time or run-time by asking when Python sees it
- Catch SyntaxError from compile(), eval(), exec() and import, but not from your own file
- Recognise IndentationError and TabError as SyntaxError subclasses
Understanding Syntax errors versus runtime exceptions
Running a Python file happens in two separate phases. First CPython reads the entire source text, parses it into a syntax tree, and compiles that tree into bytecode; only after the whole module has compiled does it start executing the first statement. A SyntaxError means the parser could not build the tree at all, so there is no bytecode and nothing runs, not even the line above the mistake. That is why a missing colon on line 400 stops a banner on line 1 from ever printing.
A runtime exception is different in kind: the source was perfectly legal Python, it compiled fine, and the failure happened while the bytecode executed because some value or name did not cooperate. int('abc') is valid syntax and an invalid conversion, so it raises ValueError only when control reaches it. Name lookups also happen at execution time, which is why a misspelled variable is a NameError rather than a syntax problem, and why the typo can sit unnoticed inside a branch nobody calls. Everything the program did before the exception, such as writing a file or appending to a list, has already happened.
The practical split follows from the phases. You cannot wrap a syntax error in a try block inside the same file, because the handler is part of the code that never got compiled; the only fixes are editing the source or letting a linter flag it before you run. When the parsing itself is deferred to runtime, though, the picture flips: compile(), eval(), exec() and import parse a new chunk of source while your program is already running, so SyntaxError arrives as an ordinary catchable exception at that call.
broken = "print('start')\nx = = 5\n"
valid = "print('start')\nx = 1 / 0\n"
try:
compile(broken, "<demo>", "exec")
except SyntaxError as e:
print("compile failed:", type(e).__name__)
code = compile(valid, "<demo>", "exec")
print("compile succeeded:", type(code).__name__)
try:
exec(code)
except ZeroDivisionError as e:
print("run failed:", type(e).__name__, "-", e)
A syntax error is found while Python compiles the source and stops the whole file from running, while an exception is raised while compiled code executes.
Worked examples
A typo that waits for you
A misspelled name compiles without complaint and only fails when the function is actually called.
def greet():
return mesage
print("module finished loading")
try:
greet()
except NameError as e:
print("NameError:", e)
Example explained
Line 1The body of greet is compiled but not executed, so 'mesage' is never looked up at compile time.
Line 2The print on the next line proves the module compiled and ran successfully.
Line 3Calling greet() executes the return statement, and the name lookup fails then, as a NameError.
Line 4Nothing about this is a syntax problem: the text is legal Python, the name simply does not exist.
Indentation problems are syntax problems
Several kinds of malformed source, all reported during compile() and all catchable as SyntaxError.
cases = [
("missing indent", "if True:\nprint('hi')\n"),
("unfinished expression", "x = 1 +\n"),
("unclosed bracket", "y = [1, 2\n"),
]
for label, src in cases:
try:
compile(src, "<demo>", "exec")
except SyntaxError as e:
print(label, "->", type(e).__name__, isinstance(e, SyntaxError))
Example explained
Line 1compile() stops at the parse phase, so it reports these without running anything.
Line 2The first case produces IndentationError, which is a subclass of SyntaxError, hence the True.
Line 3isinstance(e, SyntaxError) is True for all three, so one except clause covers every parse failure.
Line 4Each error object also carries e.lineno, e.offset and e.text describing where the parser gave up.
Parsing user input at runtime
When the source arrives while the program runs, a syntax error becomes just another exception to handle.
def try_eval(text):
try:
return eval(text)
except SyntaxError:
return "not a valid expression"
except ZeroDivisionError:
return "divided by zero"
print(try_eval("2 + 3 * 4"))
print(try_eval("2 +"))
print(try_eval("1/0"))
Example explained
Line 1eval() parses its string argument on every call, so the parse happens inside the try block.
Line 2"2 +" never becomes bytecode, and eval raises SyntaxError at the point of the call.
Line 3"1/0" parses fine and fails later, during evaluation, with ZeroDivisionError.
Line 4The two except clauses are needed precisely because the two failures come from different phases.
Important notes
IndentationError and TabError are subclasses of SyntaxError, so except SyntaxError catches all three, and except IndentationError catches only the narrower case.
SyntaxError message text changes between Python versions, so match on the exception class or on e.lineno rather than on the wording.
Common mistakes
Wrapping suspect code in try/except SyntaxError inside the same file: the handler is never compiled either, so the file still refuses to run and the message is unchanged.
Assuming a mistyped variable or function name is caught before running; it is a NameError raised only when that line executes, so a typo in an error-handling branch can survive every manual test.
Trusting the caret position as the exact broken character; with an unclosed bracket or quote the parser only notices several lines later and blames a line that looks perfectly fine.
Try it yourself
Change, predict, then run
Build a list of three source strings, one with an unclosed bracket, one with 1/0, and one that just prints a number, then loop over them calling compile() and only exec() the ones that compiled, printing whether each failed at parse or at run.
Open the Python workspaceCheck your understanding
A script prints a startup banner on its first line and is missing a colon after an if on line 40. What happens when you run it?
- Nothing is printed; the file fails while being compiled, before any statement executes
- The banner is printed, then execution stops when it reaches line 40
- The banner is printed along with lines 2 to 39, then a SyntaxError traceback appears
- The banner is printed, and the syntax error is reported only if that if statement is reached
Show answer
CPython compiles the entire module to bytecode before executing anything, so a parse failure on line 40 means line 1 never runs and no banner appears. The tempting answers describe runtime exception behaviour, where earlier statements really do execute and their output survives; a syntax error is found in an earlier phase, so reachability of line 40 is irrelevant.