Proper way to declare custom exceptions in modern Python? Proper way to declare custom exceptions in modern Python? python python

Proper way to declare custom exceptions in modern Python?


Maybe I missed the question, but why not:

class MyException(Exception):    pass

To override something (or pass extra args), do this:

class ValidationError(Exception):    def __init__(self, message, errors):                    # Call the base class constructor with the parameters it needs        super().__init__(message)                    # Now for your custom code...        self.errors = errors

That way you could pass dict of error messages to the second param, and get to it later with e.errors.

In Python 2, you have to use this slightly more complex form of super():

super(ValidationError, self).__init__(message)


With modern Python Exceptions, you don't need to abuse .message, or override .__str__() or .__repr__() or any of it. If all you want is an informative message when your exception is raised, do this:

class MyException(Exception):    passraise MyException("My hovercraft is full of eels")

That will give a traceback ending with MyException: My hovercraft is full of eels.

If you want more flexibility from the exception, you could pass a dictionary as the argument:

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

However, to get at those details in an except block is a bit more complicated. The details are stored in the args attribute, which is a list. You would need to do something like this:

try:    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})except MyException as e:    details = e.args[0]    print(details["animal"])

It is still possible to pass in multiple items to the exception and access them via tuple indexes, but this is highly discouraged (and was even intended for deprecation a while back). If you do need more than a single piece of information and the above method is not sufficient for you, then you should subclass Exception as described in the tutorial.

class MyError(Exception):    def __init__(self, message, animal):        self.message = message        self.animal = animal    def __str__(self):        return self.message


"What is the proper way to declare custom exceptions in modern Python?"

This is fine unless your exception is really a type of a more specific exception:

class MyException(Exception):    pass

Or better (maybe perfect), instead of pass give a docstring:

class MyException(Exception):    """Raise for my specific kind of exception"""

Subclassing Exception Subclasses

From the docs

Exception

All built-in, non-system-exiting exceptions are derived from this class.All user-defined exceptions should also be derived from thisclass.

That means that if your exception is a type of a more specific exception, subclass that exception instead of the generic Exception (and the result will be that you still derive from Exception as the docs recommend). Also, you can at least provide a docstring (and not be forced to use the pass keyword):

class MyAppValueError(ValueError):    '''Raise when my specific value is wrong'''

Set attributes you create yourself with a custom __init__. Avoid passing a dict as a positional argument, future users of your code will thank you. If you use the deprecated message attribute, assigning it yourself will avoid a DeprecationWarning:

class MyAppValueError(ValueError):    '''Raise when a specific subset of values in context of app is wrong'''    def __init__(self, message, foo, *args):        self.message = message # without this you may get DeprecationWarning        # Special attribute you desire with your Error,         # perhaps the value that caused the error?:        self.foo = foo                 # allow users initialize misc. arguments as any other builtin Error        super(MyAppValueError, self).__init__(message, foo, *args) 

There's really no need to write your own __str__ or __repr__. The built-in ones are very nice, and your cooperative inheritance ensures that you use them.

Critique of the top answer

Maybe I missed the question, but why not:

class MyException(Exception):    pass

Again, the problem with the above is that in order to catch it, you'll either have to name it specifically (importing it if created elsewhere) or catch Exception, (but you're probably not prepared to handle all types of Exceptions, and you should only catch exceptions you are prepared to handle). Similar criticism to the below, but additionally that's not the way to initialize via super, and you'll get a DeprecationWarning if you access the message attribute:

Edit: to override something (or pass extra args), do this:

class ValidationError(Exception):    def __init__(self, message, errors):        # Call the base class constructor with the parameters it needs        super(ValidationError, self).__init__(message)        # Now for your custom code...        self.errors = errors

That way you could pass dict of error messages to the second param, and get to it later with e.errors

It also requires exactly two arguments to be passed in (aside from the self.) No more, no less. That's an interesting constraint that future users may not appreciate.

To be direct - it violates Liskov substitutability.

I'll demonstrate both errors:

>>> ValidationError('foo', 'bar', 'baz').messageTraceback (most recent call last):  File "<pyshell#10>", line 1, in <module>    ValidationError('foo', 'bar', 'baz').messageTypeError: __init__() takes exactly 3 arguments (4 given)>>> ValidationError('foo', 'bar').message__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6'foo'

Compared to:

>>> MyAppValueError('foo', 'FOO', 'bar').message'foo'