Common Errors for Python Beginners
When starting with Python programming, beginners often encounter error messages. In this chapter, we will focus on these errors, which were not covered previously.
Python has two easily recognizable types of errors: Syntax Errors and Exceptions.
Python Assert Statement
Python's assert
is used to evaluate an expression. If the expression evaluates to false, it raises an exception.
Syntax Errors
Syntax errors, also known as parsing errors, are common among beginners. Here's an example:
>>> while True print('Hello world') File "<stdin>", line 1 while True print('Hello world') ^ SyntaxError: invalid syntax
In this example, the print()
function is missing a colon (:
) before it.
The syntax parser points out the line with the error and uses a small arrow to indicate where the error occurred.
Exceptions
Even if the syntax of a Python program is correct, errors can still occur while the program is running. These errors are called exceptions.
Most exceptions are not handled by the program and show up as error messages, as seen below:
>>> 10 * (1/0) # Division by zero, triggers an exception Traceback (most recent call last): File "<stdin>", line 1, in ? ZeroDivisionError: division by zero >>> 4 + spam*3 # 'spam' is not defined, triggers an exception Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: name 'spam' is not defined >>> '2' + 2 # Cannot concatenate int and str, triggers an exception Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate str (not "int") to str
Exceptions are displayed with different types: ZeroDivisionError
, NameError
, and TypeError
are examples shown here.
The first part of the error message displays the context in which the exception occurred, showing detailed information in the form of a traceback.
Exception Handling
try/except
You can catch exceptions using the try/except
statement.
In the following example, the user is asked to input a valid integer. If the user interrupts the program (e.g., using Control-C
or another OS signal), a KeyboardInterrupt
exception is raised.
while True: try: x = int(input("Please enter a number: ")) break except ValueError: print("That's not a number! Please try again.")
Here's how the try
statement works:
-
First, the code inside the
try
block is executed. -
If no exception occurs, the
except
block is ignored, and the code continues after thetry
block. -
If an exception occurs during the
try
block, the remaining code in the block is skipped, and if the exception matches the type specified inexcept
, that block is executed. -
If no
except
block matches, the exception is propagated up.
A try
statement can have multiple except
blocks to handle different types of exceptions, but only one will be executed.
You can also handle multiple exceptions in one block by enclosing them in parentheses:
except (RuntimeError, TypeError, NameError): pass
The last except
block can ignore the exception name and act as a wildcard. You can print an error message and re-raise the exception:
import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except OSError as err: print(f"OS error: {err}") except ValueError: print("Could not convert data to an integer.") except: print("Unexpected error:", sys.exc_info()[0]) raise
try/except...else
The try/except
statement can also have an optional else
clause, which must appear after all except
blocks. This clause is executed only if no exceptions were raised in the try
block.
Example:
for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print(f'Cannot open {arg}') else: print(f'{arg} has {len(f.readlines())} lines') f.close()
Using an else
clause is better than putting all code inside the try
block since it avoids catching exceptions that weren't meant to be caught.
Handling Exceptions in Functions
Exception handling works not only for exceptions in the try
block but also for exceptions that occur in functions called within the try
block. For example:
def this_fails(): x = 1/0 try: this_fails() except ZeroDivisionError as err: print(f'Handling run-time error: {err}')
Output:
Handling run-time error: division by zero
This overview helps beginners understand how to recognize and handle common errors in Python programming, making code more robust and maintainable.
try-finally Statement
The try-finally
statement ensures that the final code block is executed, regardless of whether an exception occurs.
In the following example, the finally
block will always be executed, whether or not an exception is raised:
try: runoob() except AssertionError as error: print(error) else: try: with open('file.log') as file: read_data = file.read() except FileNotFoundError as fnf_error: print(fnf_error) finally: print('This line will always execute, whether an exception occurred or not.')
Raising Exceptions
Python uses the raise
statement to trigger a specified exception.
The syntax for raise
is as follows:
raise [Exception [, args [, traceback]]]
In the following example, an exception is raised if x
is greater than 5:
x = 10 if x > 5: raise Exception('x cannot be greater than 5. The value of x is: {}'.format(x))
Running this code will trigger an exception:
Traceback (most recent call last): File "test.py", line 3, in <module> raise Exception('x cannot be greater than 5. The value of x is: {}'.format(x)) Exception: x cannot be greater than 5. The value of x is: 10
The only argument to raise
specifies the exception to be raised. It must be an instance of an exception or a subclass of Exception
.
If you want to check whether an exception has been raised but don’t need to handle it, a simple raise
statement can be used to re-raise the exception:
>>> try: raise NameError('HiThere') # Simulate an exception. except NameError: print('An exception flew by!') raise
This outputs:
An exception flew by! Traceback (most recent call last): File "<stdin>", line 2, in ? NameError: HiThere
User-Defined Exceptions
You can define your own exceptions by creating a new exception class. These exception classes inherit from the Exception
class, either directly or indirectly. For example:
>>> class MyError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) >>> try: raise MyError(2*2) except MyError as e: print('My exception occurred, value:', e.value)
Output:
My exception occurred, value: 4
You can also raise a user-defined exception like this:
>>> raise MyError('oops!') Traceback (most recent call last): File "<stdin>", line 1, in ? __main__.MyError: 'oops!'
In this example, the default __init__()
method from the Exception
class has been overridden.
When a module is likely to raise different exceptions, it’s common practice to create a base exception class for that module, and then define subclasses for specific error conditions:
class Error(Exception): """Base class for exceptions in this module.""" pass class InputError(Error): """Exception raised for errors in the input. Attributes: expression -- input expression in which the error occurred message -- explanation of the error """ def __init__(self, expression, message): self.expression = expression self.message = message class TransitionError(Error): """Raised when an operation attempts a state transition that is not allowed. Attributes: previous -- state before the transition next -- attempted new state message -- explanation of why the transition is not allowed """ def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
Most exception names end with "Error," just like standard exceptions.
Defining Cleanup Behavior
The try
statement has an optional clause that defines cleanup actions, which are executed no matter what. For example:
>>> try: ... raise KeyboardInterrupt ... finally: ... print('Goodbye, world!') ... Goodbye, world!
Output:
Traceback (most recent call last): File "<stdin>", line 2, in <module> KeyboardInterrupt
In this example, the finally
clause is executed whether or not an exception is raised in the try
block.
If an exception is raised in the try
block (or in an except
or else
block) and isn’t handled by any except
clause, the exception is re-raised after the finally
block is executed.
Here's a more complex example that includes both except
and finally
clauses in the same try
statement:
>>> def divide(x, y): try: result = x / y except ZeroDivisionError: print("division by zero!") else: print("result is", result) finally: print("executing finally clause")
>>> divide(2, 1) result is 2.0 executing finally clause >>> divide(2, 0) division by zero! executing finally clause >>> divide("2", "1") executing finally clause Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 3, in divide TypeError: unsupported operand type(s) for /: 'str' and 'str'
Predefined Cleanup Actions
Some objects define standard cleanup actions that are automatically executed when the object is no longer needed, regardless of whether the operation was successful.
The following example demonstrates opening a file and printing its contents:
for line in open("myfile.txt"): print(line, end="")
However, this code leaves the file open after it finishes executing.
The with
statement ensures that objects like files are properly cleaned up after use, even if an error occurs:
with open("myfile.txt") as f: for line in f: print(line, end="")
Once the with
block is exited, the file f
is automatically closed, even if an error occurs during the process.