sec03 - Python Exception Handling Guide: Types of Errors and Safe Coding Practices
スポンサーリンク

What is an Error?

Sometimes unexpected problems occur while running a program. These are called "errors." When an error occurs, the program may stop immediately.

Python mainly has two types of errors:

  • SyntaxError: Occurs when the Python syntax is violated.
  • RuntimeError/Exception: Occurs when the program's syntax is correct, but a problem arises during execution.

As a basic example, let's see what happens when dividing by zero.

x = 10
y = 0
z = x / y  # ZeroDivisionError occurs
ZeroDivisionError: division by zero

In this code, dividing by 0 raises a ZeroDivisionError, and the program stops.

Common Types of Errors

Understanding common exceptions in Python and the situations in which they occur helps you quickly identify the cause of errors.

Typical examples:

  • ZeroDivisionError: Occurs when dividing by 0
  • ValueError: Occurs when trying to convert a string to a number but failing
  • TypeError: Occurs when performing an operation on incompatible types

Sample code:

# Example of ValueError
s = 'abc'
num = int(s)  # ValueError occurs

# Example of TypeError
num = 10
text = '5'
result = num + text  # TypeError occurs
# Example of ValueError
ValueError: invalid literal for int() with base 10: 'abc'

# Example of TypeError
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Understanding what these exceptions mean allows you to quickly identify the cause.

スポンサーリンク

Catching Exceptions with try/except

The basic way to safely handle exceptions in Python is try/except. If an error occurs inside the try block, it is handled in the except block.

try:
    x = 10 / 0
except ZeroDivisionError:
    print('Caught ZeroDivisionError: Tried to divide by 0')
Caught ZeroDivisionError: Tried to divide by 0

Note: The ZeroDivisionError raised inside try is caught by except, allowing the program to display an error message without stopping.

Using as e:

By using as e after except, you can assign the exception object to a variable and obtain detailed information.

try:
    x = 10 / 0
except ZeroDivisionError as e:
    print('Error details:', e)
Error details: division by zero

Using as e allows you to handle not only the type of error but also detailed messages, which is useful for logging and debugging.

Catching Multiple Exceptions

Within a single try block, you can catch multiple exceptions individually.

s = 'abc'
try:
    num = int(s)
    result = 10 / num
except ValueError:
    print('Caught ValueError: Cannot convert string to number')
except ZeroDivisionError:
    print('Caught ZeroDivisionError: Tried to divide by 0')
Caught ValueError: Cannot convert string to number

Note: By providing separate except blocks for each exception, you can handle each error appropriately.

Using else and finally

You can add else and finally to a try/except structure.

  • else: Executed if no exception occurs
  • finally: Always executed, regardless of whether an exception occurs
try:
    num = int('10')
except ValueError:
    print('ValueError occurred')
else:
    print('Conversion successful:', num)
finally:
    print('Processing complete')
Conversion successful: 10
Processing complete

Note: else allows grouping actions that succeed, improving readability. finally is useful for cleanup tasks like closing files or network connections.

Exception Handling Inside Functions

Exceptions can also be handled inside functions. You can catch exceptions to safely process them or propagate them to the caller if needed.

def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print('Caught ZeroDivisionError: Tried to divide by 0')
        return None

result = safe_divide(10, 0)
print(result)
Caught ZeroDivisionError: Tried to divide by 0
None

Note: Handling exceptions inside functions allows the caller to safely use the function.

Exception Handling Inside Loops

When processing multiple data items, handling exceptions individually inside a loop ensures that one error does not stop the processing of remaining data.

numbers = ['10', '5', 'abc', '0', '8']
total = 0

for s in numbers:
    print(f'number {s}:', end='\t')
    try:
        num = int(s)
        total += 10 / num
    except ValueError:
        print(f'ValueError: "{s}" cannot be converted to a number')
    except ZeroDivisionError:
        print('ZeroDivisionError: Tried to divide by 0')
    else:
        print(f'Processed successfully [total = {total}]')

print('Total:', total)
number 10:      Processed successfully [total = 1.0]
number 5:       Processed successfully [total = 3.0]
number abc:     ValueError: "abc" cannot be converted to a number
number 0:       ZeroDivisionError: Tried to divide by 0
number 8:       Processed successfully [total = 4.25]
Total: 4.25

Note: Handling exceptions inside loops prevents a single data error from stopping the entire process.

Advanced Exercise: Incorporating Exception Handling

An advanced example reads numbers from multiple files and calculates the total. It safely handles missing files or non-numeric data.

When running in Visual Studio Code, create 'numbers1.txt', 'numbers2.txt', and 'numbers3.txt' in the same directory as the Python file and check the results.

file_list = ['numbers1.txt', 'numbers2.txt', 'numbers3.txt']
total = 0

for filename in file_list:
    try:
        with open(filename, 'r') as file:
            data = file.read()
        number = int(data)
    except FileNotFoundError:
        print(f'{filename} not found')
        continue
    except ValueError:
        print(f'Data in {filename} ("{data}") is not a number')
        continue
    else:
        print(f'Successfully read {filename}: {number}')
        total += number

print('Total:', total)
Successfully read numbers1.txt: 150
numbers2.txt not found
Data in numbers3.txt ("abc") is not a number
Total: 150

Note: This advanced exercise combines multiple exceptions. Using else groups success processing, and continue moves to the next file.

Exception Class Hierarchy

Python exceptions are organized hierarchically. All exceptions derive from BaseException, and most exceptions we handle are under the Exception class.

See Python: Built-in Exceptions for details on each error.

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    ├── PythonFinalizationError
      │    └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning

Example: ArithmeticError is the parent class of ZeroDivisionError, OverflowError, and FloatingPointError.

Understanding this hierarchy allows you to catch multiple exceptions at once or target specific exceptions.

For example, using the parent ArithmeticError catches all arithmetic-related errors:

try:
    x = 10 / 0
except ArithmeticError:
    print('An arithmetic error occurred')  # ZeroDivisionError is also caught

Conversely, catching only ZeroDivisionError limits the scope:

try:
    x = 10 / 0
except ZeroDivisionError:
    print('Tried to divide by 0')  # OverflowError and FloatingPointError are not caught
スポンサーリンク