Syntax Errors |
common error when learning Python
>>> while True print('Hello world') File "<stdin>", line 1 while True print('Hello world') ^^^^^ SyntaxError: invalid syntax |
Exceptions |
execution errors
>>> 10 * (1/0) Traceback (most recent call last): File "<stdin>", line 1, in <module> 10 * (1/0) ~^~ ZeroDivisionError: division by zero >>> 4 + spam*3 Traceback (most recent call last): File "<stdin>", line 1, in <module> 4 + spam*3 ^^^^ NameError: name 'spam' is not defined >>> '2' + 2 Traceback (most recent call last): File "<stdin>", line 1, in <module> '2' + 2 ~~~~^~~ TypeError: can only concatenate str (not "int") to strhttps://docs.python.org/3/library/exceptions.html#bltin-exceptions lists the built-in exceptions and their meanings |
Handling Exceptions |
>>> while True: ... try: ... x = int(input("Please enter a number: ")) ... break ... except ValueError: ... print("Oops! That was not a valid number. Try again...")a try statement may have more than one except clause specifying handlers for different exceptions an except clause may name multiple exceptions as a parenthesized tuple ... except (RuntimeError, TypeError, NameError): ... passclass B is derived from Exception as written the code will print B, C and D in that order if the order of the except clauses is reversed the output will be B, B, B because the first matching clause is executed class B(Exception): pass class C(B): pass class D(C): pass for cls in [B, C, D]: try: raise cls() except D: print("D") except C: print("C") except B: print("B")the except clause may specify a variable after the exception name builtin exception types define __str__() to print all the arguments without explicitly accessing .args >>> try: ... raise Exception('spam', 'eggs') ...except Exception as inst: ... print(type(inst)) # the exception type ... print(inst.args) # arguments stored in .args ... print(inst) # __str__ allows args to be printed directly, ... # but may be overridden in exception subclasses ... x, y = inst.args # unpack args ... print('x =', x) ... print('y =', y) ... <class 'Exception'> ('spam', 'eggs') ('spam', 'eggs') x = spam y = eggsBaseException is the common base class of all exceptions Exception derived from BaseException is the base class of all the non-fatal exceptions Exceptions which are not subclasses of Exception are not typically handled they are used to indicate that the program should terminate most common pattern for handling Exception is to print or log the exception and then re-raise it let the caller also handle the exception import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print("OS error:", err) except ValueError: print("Could not convert data to an integer.") except Exception as err: print(f"Unexpected {err=}, {type(err)=}") raiseThe try ... except statement has an optional else clause the statement must follow all except clauses code that must be executed if the try clause does not raise an exception for arg in sys.argv[1:]: try: f = open(arg, 'r') except OSError: print('cannot open', arg) else: print(arg, 'has', len(f.readlines()), 'lines') f.close() |
Raising Exceptions |
the raise
statement allows the forcing of a specified exception to occur
>>> raise NameError('HiThere') Traceback (most recent call last): File "<stdin>", line 1, in <module> raise NameError('HiThere') NameError: HiThereto re-raise an exception >>> try: ... raise NameError('HiThere') ... except NameError: ... print('An exception flew by!') ... raise An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in <module> raise NameError('HiThere') NameError: HiThere |
Exception Chaining |
if an unhandled exception occurs inside an except section, it will have the exception being handled attached to it and included in the error message
>>> try: ... open("database.sqlite") ... except OSError: ... raise RuntimeError("unable to handle error") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> open("database.sqlite") ~~~~^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 4, in <module> raise RuntimeError("unable to handle error") RuntimeError: unable to handle errorthe raise statement allows an optional from clause to indicate that an exception is a direct consequence of another exception # exc must be exception instance or None. raise RuntimeError from excuseful when transforming exceptions >>> def func(): ... raise ConnectionError ... >>> try: ... func() ... except ConnectionError as exc: ... raise RuntimeError('Failed to open database') from exc ... Traceback (most recent call last): File "<stdin>", line 2, in <module> func() ~~~~^^ File "<stdin>", line 2, in func ConnectionError The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> raise RuntimeError('Failed to open database') from exc RuntimeError: Failed to open database |
Defining Clean-up Actions |
finally clause defines clean-up actions that must be executed under all circumstances
>>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') Goodbye, world! Traceback (most recent call last): File "<stdin>", line 2, in <module> raise KeyboardInterrupt KeyboardInterruptif a finally clause is present, the finally clause will execute as the last task before the try statement completes the finally clause runs whether or not the try statement produces an exception
|
Predefined Clean-up Actions |
some objects have predefined clean-up actions
with open("myfile.txt") as f: for line in f: print(line, end="")with keyword closes the file when the block is exited |
Raising and Handling Multiple Unrelated Exceptions |
situations where it is necessary to report several exceptions that have occurred often the case in concurrency frameworks when several tasks may have failed in parallel other use cases where it is desirable to continue execution and collect multiple errors rather than raise the first exception ExceptionGroup wraps a list of exception instances so that they can be raised together ExceptionGroup is a subclass of Exception and can be handled like any other exception >>> def f(): ... excs = [OSError('error 1'), SystemError('error 2')] ... raise ExceptionGroup('there were problems', excs) >>> f() + Exception Group Traceback (most recent call last): | File "<stdin>", line 1, in <module> | f() | ~^^ | File "<stdin>", line 3, in f | raise ExceptionGroup('there were problems', excs) | ExceptionGroup: there were problems (2 sub-exceptions) +-+---------------- 1 ---------------- | OSError: error 1 +---------------- 2 ---------------- | SystemError: error 2 +------------------------------------ >>> f() ... except Exception as e: ... print(f'caught {type(e)}: e') caught <class 'ExceptionGroup'>: euse except* instead of except to selectively handle only the exceptions in the group that match a certain type unhandled exceptions in the group propagate to the next handler >>> def f(): ... raise ExceptionGroup( ... "group1", ... [ ... OSError(1), ... SystemError(2), ... ExceptionGroup( ... "group2", ... [ ... OSError(3), ... RecursionError(4) ... ] ... ) ... ] ... ) ... >>> try: ... f() ... except* OSError as e: ... print("There were OSErrors") ... except* SystemError as e: ... print("There were SystemErrors") There were OSErrors There were SystemErrors + Exception Group Traceback (most recent call last): | File "<stdin>", line 2, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise ExceptionGroup( | ...<12 lines>... | ) | ExceptionGroup: group1 (1 sub-exception) +-+---------------- 1 ---------------- | ExceptionGroup: group2 (1 sub-exception) +-+---------------- 1 ---------------- | RecursionError: 4 +------------------------------------exceptions nested in an ExceptionGroup must be instances pattern for 'collecting' exceptions >>> excs = [] ... for test in tests: ... try: ... test.run() ... except Exception as e: ... excs.append(e) >>> if excs: ... raise ExceptionGroup("Test Failures", excs) |
Enriching Exceptions with Notes |
it can be useful to add information after an exception was caught exceptions have a method add_note(note) that accepts a string and adds it to the exception's notes list standard traceback rendering includes all notes, in the order they were added, after the exception >>> try: ... raise TypeError('bad type') ... except Exception as e: ... e.add_note('Add some information') ... e.add_note('Add some more information') .. raise Traceback (most recent call last): File "<stdin>", line 2, in <module> raise TypeError('bad type') TypeError: bad type Add some information Add some more informationcan also add context information to an exception >>> def f(): ... raise OSError('operation failed') ... >>> excs = [] >>> for i in range(3): ... try: ... f() ... except Exception as e: ... e.add_note(f'Happened in Iteration {i+1}') ... excs.append(e) raise ExceptionGroup('We have some problems', excs) + Exception Group Traceback (most recent call last): | File "<stdin>", line 1, in <module> | raise ExceptionGroup('We have some problems', excs) | ExceptionGroup: We have some problems (3 sub-exceptions) +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 1 +---------------- 2 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 2 +---------------- 3 ---------------- | Traceback (most recent call last): | File "<stdin>", line 3, in <module> | f() | ~^^ | File "<stdin>", line 2, in f | raise OSError('operation failed') | OSError: operation failed | Happened in Iteration 3 +------------------------------------ |