1

As I understand it, I can use the abc module in Python to create abstract classes that can't be instantiated (amongst other nice properties). I tried to use this to create a hierarchy of Exception classes to represent various exit codes for my application, but I'm still able to instantiate my base class, even though I don't want that to happen. Here's some code that demonstrates the problem:

#!/usr/bin/env python3

import abc

class ExitCodeException(Exception):
    __metaclass__ = abc.ABCMeta

    def __init__(self, message):
        super().__init__()
        self._message = message

    @abc.abstractmethod
    def getExitCode(self):
        """Return the exit code for this exception"""
        return

class FatalException(ExitCodeException):
    def getExitCode(self):
        return 1

raise ExitCodeException("Oh no!")

I was expecting my program to quit with an exception saying that ExitCodeException couldn't be instantiated, but instead I just get the standard stack trace I'd expect if ExitCodeException weren't abstract:

Traceback (most recent call last)
  File "./email2pdf_classexception", line 21, in <module>
    raise ExitCodeException("Oh no!")
__main__.ExitCodeException

How can I fix this?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Andrew Ferrier
  • 16,664
  • 13
  • 47
  • 76
  • 3
    In Python3 you should use keyword syntax: `class ExitCodeException(Exception, metaclass = abc.ABCMeta):`. However this is not enough to fix your problem. The `Exception` base class make instantiation possible - without it, all works as expected. But I imagine it does not solve your problem, as you want to derive throwable exceptions from this base class. – BartoszKP Oct 18 '14 at 17:45
  • 2
    Firstly, syntax of defining metaclass has been changed in Python3. It has to be passed as a keyword argument (called `metaclass`) to the class definition (ref: https://docs.python.org/3.3/reference/datamodel.html#customizing-class-creation ). Secondly, I an ABC can't have a base class which is not a ABC. So, you cannot have `Exception` (which is not a ABC) as the base class of `ExitCodeException` if you want to make `ExitCodeException` an ABC. So, I don't think there is a straight forward way to make your code work. – Debanshu Kundu Oct 18 '14 at 19:00
  • 1
    Why is it important to not be able to instantiate the base class? If you don't want to instantiate it, just don't instantiate it. This feels like trying to write Java in Python. – Ned Batchelder Oct 18 '14 at 19:08
  • 1
    @BartoszKP, Debanshu Kundu: you've both put the same answer in a comment. Put it in an answer! :) – Ned Batchelder Oct 18 '14 at 19:14
  • @NedBatchelder, well, it's generally a good principle not to be able to instantiate a class with an abstract method (which I'm trying to do here). Java does have that feature, you're right. As a beginning Pythoner, this feels like a limitation - albeit, not a serious one. – Andrew Ferrier Oct 18 '14 at 19:19
  • @NedBatchelder Haven't found any sources to support my guess. Not sure if it's intended (probably) or is it a bug - so I didn't post an answer :) Nor I came up with any workaround - thus the answer posted by OP seems all right. – BartoszKP Oct 18 '14 at 19:36
  • 1
    @AndrewFerrier: Python is not a strict language. There are a lot of things you shouldn't do that Python will not prevent you from doing. Who is trying to instantiate your base exception? Why are they doing that? This feels like looking for a solution to a non-problem. – Ned Batchelder Oct 18 '14 at 19:54

1 Answers1

3

As discussed in the comments by @BartoszKP and @Debanshu Kundu above, it appears the concrete superclass Exception is what causes the issue here. As such, I've come up with a slightly different pattern which seems to work (as I understand it, this is an older-style of pattern from Python 2, but still seems valid):

#!/usr/bin/env python3

class ExitCodeException(Exception):
    def __new__(cls, *args, **kwargs):
        if cls is ExitCodeException:
            raise NotImplementedError("Base class may not be instantiated")
        return Exception.__new__(cls, *args, **kwargs)

    def __init__(self, message):
        super().__init__()
        self._message = message

    def getExitCode(self):
        """Return the exit code for this exception"""
        return

class FatalException(ExitCodeException):
    def getExitCode(self):
        return 1

raise FatalException("Oh no!")

This works as intended; if I change the code to instantiate ExitCodeException directly, it fails.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Andrew Ferrier
  • 16,664
  • 13
  • 47
  • 76