Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info
titleUpdateUpdates
  • 2019-01-26: added is_error() and perror_exit(); other updates
  • 2018-01-20: renamed err to erro to avoid possible name conflict
  • 2017-11-08: the section on double on double assignment was added.

toc-local

Introduction

...

I have used the following in a number of projects (see repo for full implementation):

Code Block
languagepy
titleerrors.py
#
# errors.py
#

import sys

class Error:
    """Base error object.
    """
    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return str(self.msg)

    def __repr__(self):
        return """<Error msg="%s">""" % str(self.msg)

Error allows me to:

...



def __perror(erro):
    sys.stderr.write("%s\n" % str(erro))

def is_error(erro):
    return isinstance(erro, Error)

def perror(erro):
    """On Error, write string to stderr.
    """
    if is_error(erro):
        __perror(erro)

def perror_exit(erro, exitcode=1):
    """Call perror(). Exit on non-zero exitcode.
    """
    if is_error(erro):
        __perror(erro)
        if exitcode != 0:
            sys.exit(exitcode)

Notes:

  • Error indicates an error situation
  • Error provides an arbitrary, but useful, text description of the error
  • pass it Error can 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

Use Cases

Subroutine

The first most basic case is when the a callable acts like a subroutine (no value is returned explicitly; in which case Python returns None):

...

This is easy to use because a None return value will always be treated as success, as anything else will be an Error.The second is when the

Function

When a callable acts like a function (, a value is returned):always returned. In such a case, an Error might be one possible values.

Code Block
languagepy
val = myfunc()
if isinstance(val, Error):
	...

This situation requires a bit more effort to use, but Python makes it easy because val can be any value, including Error.

A third use case is really just a different flavor of the aboveThis can also be written using is_error():

Code Block
val = myfunc()
if is_error(val):
    ...

is_error() cannot be a method of Error since myfunc() can return a non-Error value.

Multivalue Return

Python can return multiple values from a callable:

Code Block
languagepy
val, erro = myfunc()
if erro:
    ...

This one 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:

Code Block
if val, erroerr = myfunc(); erroerr !=  nil {
	...
}

...

Double Assignment

It is also possible to use a "double assignment":

Code Block
val = erro = myfunc()
if isinstanceis_error(erro, Error):
    ...
# use the result bound to "val"

There are some clear benefits of this approach:

  1. the code is succinct,
  2. that the function/method returns a non-error or an error result is documented by the double assignment,
  3. 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.

Extending Error

Of course, the Error class can be extended to provide a collection of classes to more precisely indicate the type of error. E.g.,

...

Code Block
languagepy
erro = myfunc()
if isinstance(erro, ErrorOverflow):
	...
elif isinstance(erro, ErrorUnderflow):
    ...
elif ininstanceisinstance(erro, Error):
    # catch all
    ...

...