Errors are typically indicated by a non-zero integer value. Within a program/library, which is the main focus here, error return values are negative. For a program, error exit values are positive. In both cases, a zero value indicates success.
Integers do not carry much information. And what information they do carry often needs to be looked up in a table (e.g., what is
errno 11?). On the other hand, a (good) string is explanatory and can be tailored to specific situations. This is why error messages are often output to stderr, even when a non-zero exit code is returned. But error strings are not just useful for end users. Even with libraries, error strings can be useful for logging. But this is more than a discussion of error strings or not.
One of the alternatives to integer-based errors in Python is to use exceptions. This can be useful, but still, it is an exception rather than just an "error". The exception vs error debate is not addressed here, but see LBYL and EAFP for more on Python's philosophy re: errors and exceptions.
The point of this article is to present an
Error class in the spirit of Go error handling and consider its use/application in Python from a personal perspective.
I have used the following in a number of projects (see repo for full implementation):
Errorindicates an error situation
Errorprovides an arbitrary, but useful, text description of the error
Errorprovide an optional error code (should be an integer)
Errorcan be passed around as an error object rather than an undifferentiated integer
is_error()is succinct and clear label for test
perror_exit()supports commonly used functionality
The most basic case is when a callable acts like a subroutine (no value is returned explicitly; in which case Python returns
This is easy to use because a
None return value will always be treated as success, as anything else will be an
When a callable acts like a function, a value is always returned. In such a case, an
Error might be one possible values.
This situation requires a bit more effort to use, but Python makes it easy because
val can be any value, including
This can also be written using
is_error() cannot be a method of
myfunc() can return a non-
Python can return multiple values from a callable:
This form is very familiar to Go programmers and avoids the overloading of the single return value case. The main difference is that Go provides a more efficient way to write this:
It is also possible to use a "double assignment":
There are some clear benefits of this approach:
- the code is succinct
- that the function/method returns a non-error or an error result is documented by the double assignment
- the result (error or not) can be accessed using a suitably named variable, as the code warrants
The only potential drawback is the cost of the double assignment. But given the use cases of Python, this likely has minimal overall performance impact while the readability and understandability of the code increases.
Of course, the
Error class can be extended to provide a collection of classes to more precisely indicate the type of error. E.g.,
These can then be tested for using
isinstance() to match
Error or the specific subclasses.
In the original article,
Error was subclassed to create
ErrorCode which took an exit code. This functionality has been incorporated into the base
Error class as an error code.
Some Pros and Cons
One of the main drawbacks is that
Error is unique to my package. It would be nice if there was a canonical
Error (as there is an
Exception). However, it may not be as much of a problem as it first appears. If errors from one layer need to be passed to another, a conversion between different, unrelated
Error implementations can be done trivially:
After all, the programmer should be aware of interfaces/interactions between layers and take care of checking and returning appropriate values. And, as presented,
Error always provides a simple text string.
A perceived drawback is that this approach encourages a lot of error testing, a practice which Go programmers typically defend as being necessary for code to be good: errors should be checked.
The primary benefit of
Error over a simple error code is that useful, contextual information can percolate up from anywhere in the code and, if appropriate, be used to inform the user. Too often, a helpful error code is converted to a generic error code and returned, thus losing information. Code-wise, the
Error type is useful; people-wise, the text is useful.
Even in Python, exceptions are not always the best way to do things. Sometimes returning an error code or similar is appropriate. In such situations, wherever one lands on the question of using something like
Error instead of integer return values for errors, the approach is worth considering.
Error is not always appropriate, but it does make one think about the merits of how and why things are done as they are.
- 2019-02-20: the base
Errorclass has been enhanced to support an error code
- 2019-01-26: added
perror_exit(); other updates
- 2018-01-20: renamed
erroto avoid possible name conflict
- 2017-11-08: the section on double assignment was added.