What\'s the proper way to declare custom exception classes in modern Python? My primary goal is to follow whatever standard other exception classes have, so that (for instan
No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.
When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.
And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do
try:
...
except NelsonsExceptions:
...
And in that case you can do the __init__ and __str__
needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.
In any case, you only need the __init__ or __str__
if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class. ;)
see how exceptions work by default if one vs more attributes are used (tracebacks omitted):
>>> raise Exception('bad thing happened')
Exception: bad thing happened
>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')
so you might want to have a sort of "exception template", working as an exception itself, in a compatible way:
>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened
>>> raise nastyerr()
NastyError: bad thing happened
>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')
this can be done easily with this subclass
class ExceptionTemplate(Exception):
def __call__(self, *args):
return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass
and if you don't like that default tuple-like representation, just add __str__
method to the ExceptionTemplate
class, like:
# ...
def __str__(self):
return ': '.join(self.args)
and you'll have
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
See a very good article "The definitive guide to Python exceptions". The basic principles are:
BaseException.__init__
with only one argument.There is also information on organizing (in modules) and wrapping exceptions, I recommend to read the guide.
To define your own exceptions correctly, there are a few best practices that you should follow:
Define a base class inheriting from Exception
. This will allow to easily catch any exceptions related to the project:
class MyProjectError(Exception):
"""A base class for MyProject exceptions."""
Organizing the exception classes in a separate module (e.g. exceptions.py
) is generally a good idea.
To create a specific exception, subclass the base exception class.
To add support for extra argument(s) to a custom exception, define a custom __init__()
method with a variable number of arguments. Call the base class's __init__()
, passing any positional arguments to it (remember that BaseException/Exception expect any number of positional arguments):
class CustomError(MyProjectError):
def __init__(self, *args, **kwargs):
super().__init__(*args)
self.foo = kwargs.get('foo')
To raise such exception with an extra argument you can use:
raise CustomError('Something bad happened', foo='foo')
This design adheres to the Liskov substitution principle, since you can replace an instance of a base exception class with an instance of a derived exception class. Also, it allows you to create an instance of a derived class with the same parameters as the parent.
"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"""
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 this class.
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 builtin ones are very nice, and your cooperative inheritance ensures that you use it.
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').message
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
ValidationError('foo', 'bar', 'baz').message
TypeError: __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'