HiveBrain v1.2.0
Get Started
← Back to all entries
debugpythonMinor

Raising error if method not overridden by sub-class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
errorclassmethodraisingsubnotoverridden

Problem

Background

I have a base (only 2 classes inherit from it, and only from it) abstract (meaning I don't want it to be used directly) class that implements some common functionality.

Some of it depends on certain methods being defined, methods whose implementation differs per the exact sub-class; so I set-up a mechanism to raise an exception in case that kind of thing happened (better explicit fail than subtle debugging, right?).

Code

class Matrix:
"""Model an abstract base interface for 2-d matrices."""

def __init__(self):
"""Raise error as class is abstract."""
self.__eq__ = self._flip = self._rotate = self.__error
raise NotImplementedError("{} should not be instantiated directly".format(type(self)))

def __error(self):
"""Raise error as certain methods need to be over-rided."""
raise NotImplementedError("method needs to be defined by sub-class")


Question

Is that an anti-pattern, like singletons or w/e? If so, what's a better way? Should I even check for this at all (i.e., maybe similar to excessive type-checking)?


Disclaimer: Python was my first language, and I do NOT like Java (although I have some experience from Android development); so this is not me trying to port some ruddy static pattern from another language.

Solution

With multi-inheritance and super, raising NotImplementedError is an anti-pattern.

Breaking the DRY principle

When __init__ raise a exception all subclass must repeat the initialization instead of using the default.

class BadBaseMatrix():
    """Init raise NotImplementedError"""
    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         # ...
         raise NotImplementedError

class ComplexMatrix(BadBaseMatrix):
    def __init__(self, text_version_of_matrix):
        self.text_version_of_matrix = text_version_of_matrix
        # ...

class OtherMatrix(BadBaseMatrix):
    def __init__(self, text_version_of_matrix):
        # Must redo the initialization here
        self.text_version_of_matrix = text_version_of_matrix
        # ...


instead of something like this

class BetterBaseMatrix():
    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         self.text_version_of_matrix = text_version_of_matrix

class ComplexMatrix(BetterBaseMatrix):
    # ...

class PrintMatrix(BetterBaseMatrix):
    def __init__(self, text_version_of_matrix):
        super().__init__(text_version_of_matrix)
        # in python 2, this would be super(MyMatrix, self)
        print("PrintMatrix initialized")


Breaking inheritance

class MyMatrix(BadBaseMatrix):
     def__init__(self, text_version_of_matrix):
        # I will use his implementation because it must do some important initialization there. 
        super().__init__(text_version_of_matrix)

>>> matrix = MyMatrix("")
# NotImplementedError


Do you really need a abstract base class

I feel that in Python, you do not need to prevent consumers of using your class a certain way. Maybe the base class could be used as a valid container. If so, returning a correct default (possibly None) would be enough for methods.

Here a similar version as your original of a plain matrix usable as a base class. But you should define each method to be able to add it's docstring.

class PlainMatrix():
    def _do_nothing(*args, **kwargs):
        """This docstring is used for all methods ..."""
        pass  # 
    rotate = flip = _do_nothing


You really need a a abstract class

Use abc.ABCMeta.

import abc

class BaseMatrix(metaclass=abc.ABCMeta):
    # In python 2, 
    # __metaclass__ = abc.ABCMeta

    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         self.text_version_of_matrix = text_version_of_matrix

    @abc.abstractmethod
    def rotate(self, degree):
         """ Abstract rotate that must be implemented in subclass"""
         pass

 class SubMatrix(BaseMatrix):

    def rotate(self, degree):
        """ True implementation of rotate"""
        # ...

 class StillAbstractMatrix(BaseMatrix):
     """ StillAbstractMatrix has not implemented rotate """
     pass

 >>> sub_matrix = SubMatrix("1 2 3")
 >>> bad = StillAbstractMatrix("1 2 3")
 # Traceback (most recent call last):
 #    File "", line 1, in 
 # TypeError: Can't instantiate abstract class StillAbstractMatrix with abstract methods rotate

Code Snippets

class BadBaseMatrix():
    """Init raise NotImplementedError"""
    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         # ...
         raise NotImplementedError

class ComplexMatrix(BadBaseMatrix):
    def __init__(self, text_version_of_matrix):
        self.text_version_of_matrix = text_version_of_matrix
        # ...

class OtherMatrix(BadBaseMatrix):
    def __init__(self, text_version_of_matrix):
        # Must redo the initialization here
        self.text_version_of_matrix = text_version_of_matrix
        # ...
class BetterBaseMatrix():
    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         self.text_version_of_matrix = text_version_of_matrix

class ComplexMatrix(BetterBaseMatrix):
    # ...

class PrintMatrix(BetterBaseMatrix):
    def __init__(self, text_version_of_matrix):
        super().__init__(text_version_of_matrix)
        # in python 2, this would be super(MyMatrix, self)
        print("PrintMatrix initialized")
class MyMatrix(BadBaseMatrix):
     def__init__(self, text_version_of_matrix):
        # I will use his implementation because it must do some important initialization there. 
        super().__init__(text_version_of_matrix)

>>> matrix = MyMatrix("")
# NotImplementedError
class PlainMatrix():
    def _do_nothing(*args, **kwargs):
        """This docstring is used for all methods ..."""
        pass  # 
    rotate = flip = _do_nothing
import abc

class BaseMatrix(metaclass=abc.ABCMeta):
    # In python 2, 
    # __metaclass__ = abc.ABCMeta

    def __init__(self, text_version_of_matrix):
         """text_version_of_matrix represent some argument to initialize all matrix"""
         self.text_version_of_matrix = text_version_of_matrix

    @abc.abstractmethod
    def rotate(self, degree):
         """ Abstract rotate that must be implemented in subclass"""
         pass

 class SubMatrix(BaseMatrix):

    def rotate(self, degree):
        """ True implementation of rotate"""
        # ...

 class StillAbstractMatrix(BaseMatrix):
     """ StillAbstractMatrix has not implemented rotate """
     pass

 >>> sub_matrix = SubMatrix("1 2 3")
 >>> bad = StillAbstractMatrix("1 2 3")
 # Traceback (most recent call last):
 #    File "<stdin>", line 1, in <module>
 # TypeError: Can't instantiate abstract class StillAbstractMatrix with abstract methods rotate

Context

StackExchange Code Review Q#47059, answer score: 9

Revisions (0)

No revisions yet.