Andrew Ferrier
Andrew Ferrier

Reputation: 17752

Why am I able to instantiate my Abstract Base Class in Python?

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?

Upvotes: 1

Views: 421

Answers (1)

Andrew Ferrier
Andrew Ferrier

Reputation: 17752

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.

Upvotes: 3

Related Questions