1

The Python docs state:

Programs may name their own exceptions by creating a new exception class (see Classes for more about Python classes). Exceptions should typically be derivedfrom the Exception class, either directly or indirectly.

...

When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions.

From Python’s super() considered super!:

Each level strips-off the keyword arguments that it needs so that the final empty dict can be sent to a method that expects no arguments at all (for example, object.init expects zero arguments)

Suppose I have the following StudentValueError and MissingStudentValue exceptions.

class StudentValueError(Exception):
    """Base class exceptions for Student Values"""
    def __init__(self, message, **kwargs):
        super().__init__(**kwargs)
        self.message = message # You must provide at least an error message.


class MissingStudentValue(StudentValueError):
    def __init__(self, expression, message, **kwargs):
        super().__init__(message, **kwargs)
        self.expression = expression

    def __str__(self):
        return "Message: {0} Parameters: {1}".format(self.message, self.expression)

I want to create exceptions that are co-operative. I have two questions:

  1. In that case, the Exception class constructor expects zero arguments (empty dict), correct?
  2. Does my example violate LSP?

The accepted answer provided here inherits from ValueError.

1 Answers1

0

Exception takes no keyword arguments, it takes only variable amount of positional parameters via *args, so you need to change **kwargs to *args. Also I would recommend to pass message and expression together with *args to super() call. After all, the example, which probably doesn't violate LSP:

class StudentValueError(Exception):
    """Base class exceptions for Student Values"""
    def __init__(self, message='', *args):
        super().__init__(message, *args)
        self.message = message 


class MissingStudentValue(StudentValueError):
    def __init__(self, message='', expression='', *args):
        super().__init__(message, expression, *args)
        self.expression = expression

    def __str__(self):
        return "Message: {0} Parameters: {1}".format(self.message, self.expression)


e = Exception('message', 'expression', 'yet_another_argument')
print(e)
e = StudentValueError('message', 'expression', 'yet_another_argument')
print(e)
e = MissingStudentValue('message', 'expression', 'yet_another_argument')
print(e)
e = MissingStudentValue()
print(e)
sanyassh
  • 8,100
  • 13
  • 36
  • 70
  • Thanks for your answer. Does it say anywhere in the documentation that `Exception` takes `*args`? Because even the linked answer uses `**kwargs`. Does my example in my question violate LSP? –  Mar 17 '19 at 19:03
  • 1
    Unfortunately, I can't find info about it in the doc. I tested it at runtime: trying to pass a keyword argument like `e = MissingStudentValue('message', 'expression', something='something')` leads to an error: `TypeError: Exception does not take keyword arguments` when calling `super()` – sanyassh Mar 17 '19 at 19:29
  • I see, but does my example violate LSP as it is? –  Mar 17 '19 at 19:32
  • Yes, and I realized that my answer also vilotes it. It is needed to pass a default value to all arguments, because base class `Exception` can be instanciated without any arguments like `e = Exception()`. I updated my answer and now custom exceptions also can be initialized without passing any arguments (see last example). – sanyassh Mar 17 '19 at 19:39
  • So, your code violates because `e = Exception()` can be done but `e = MissingStudentValue()` can not, it will raise `TypeError: __init__() missing 2 required positional arguments: 'message' and 'expression'` – sanyassh Mar 17 '19 at 19:40
  • Why do we need to pass both `message`, and `expression` up the call chain? Why can't we just pass it to the class that needs it? You can edit your answer instead of answering in the comments. If would be easier if the documentation were more clear and it was easy to find the `.py` file for Exceptions. –  Mar 17 '19 at 19:54
  • In this case passing `expression` is optional. You will feel difference only when calling `print(Exception.__str__(e))`. Exceptions are built-in, they are written in C and there are no .py files for them. – sanyassh Mar 17 '19 at 20:00
  • I have a followup question, and after waiting one day, no body cared to answer. https://stackoverflow.com/questions/55213172/what-arguments-does-the-built-in-exception-class-require?noredirect=1&lq=1 –  Mar 18 '19 at 14:02
  • I hope that somebody can answer it, I can't. And yes, sometimes question stays unanswered for a long time. – sanyassh Mar 18 '19 at 14:22
  • I reached out to Yury Selivanov, a Python Core Dev, and he said that calling `super().__init__(*args)` is the right approach to pass any remaining arguments to the base `Exception`. –  Mar 18 '19 at 16:04