In the , choosing IOError as a replacement for FileNotFoundError is functional but not ideal. While it raises the level of abstraction from “file not found” to “input/output error,” that abstraction is still too low-level for, say, an enterprise-scale data management system.
A better approach is to define a custom exception type that reflects the problem in domain-specific terms. For example, you could define a new exception type called DataLoadError. Custom exceptions are typically subclasses of the built-in class Exception:
| | >>> class DataLoadError(Exception): |
| | ... """Custom exception for data loading errors.""" |
| | ... |
| | >>> help(DataLoadError) |
| | Help on class DataLoadError in module __main__: |
| | |
| | class DataLoadError(builtins.Exception) |
| | | Custom exception for data loading errors. |
| | | |
| | | Method resolution order: |
| | ... |
Inheritance Matters! | |
|---|---|
| | Inherit custom exceptions from Exception, not BaseException. BaseException is for system-exiting errors. |
The body of a custom exception class can be empty except for a docstring that documents its purpose (you may want to revisit ). Even this minimal definition provides two major benefits:
Your code communicates intent more clearly.
Future readers (or your future self) will immediately understand the context and purpose of the exception.
You can now rewrite the previous example in terms of this domain-specific exception:
| | >>> def load_data(filename: str) -> str: |
| | ... """Return the contents of the file `filename`.""" |
| | ... try: |
| | ... with open(filename) as infile: |
| | ... return infile.read() |
| | ... except FileNotFoundError as eo: |
| | ... raise DataLoadError(f'Could not load data from {filename}') from eo |
| | ... |
| | >>> try: |
| | ... load_data('missing.txt') |
| | ... except DataLoadError as eo: |
| | ... print(eo) |
| | ... print('Original cause: {eo.__cause__}') |
| | ... |
| | Could not load data from missing.txt |
| | Original cause: [Errno 2] No such file or directory: 'missing.txt' |
The functionality of the code remains the same, but its readability and maintainability improve significantly. Instead of leaking low-level implementation details (FileNotFoundError), the function communicates a higher-level concept (DataLoadError) that better matches the language of the application or domain.