9
\$\begingroup\$

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.

\$\endgroup\$

3 Answers 3

9
\$\begingroup\$

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 "<stdin>", line 1, in <module>
 # TypeError: Can't instantiate abstract class StillAbstractMatrix with abstract methods rotate
\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the detailed answer; definitely got a lot about abstract classes from it. That said, do you even think I need to define these subclass-specific methods in the base one? I mean, they'd just error out naturally, wouldn't they? \$\endgroup\$ Apr 14, 2014 at 6:39
  • \$\begingroup\$ In your own code, not used by others, almost a throwaway project, you do not need anything special. But many tools could help you more if they are define: IDE autocomplete and rename, documentation builder, ... Also I think that defining them make the intention/contract of the base class clearer: what need to be override, what method can I used with descendants. \$\endgroup\$ Apr 16, 2014 at 13:58
  • \$\begingroup\$ You're right. I was over-thinking the method access part, and under-thinking the actual architecture. Thanks! \$\endgroup\$ Apr 17, 2014 at 8:20
1
\$\begingroup\$

Perhaps you could write a small wrapper to confirm that self isn't of type base class. This way you don't need to implement it, unless you plan on using it.

def confirm_subclass(func):
    def base_func(self, *args, **kwargs):
        if type(self) == Matrix:
            raise NotImplementedError("method: {} needs to be defined by sub-class: ".format(func, type(self)))
        else:
            return self.func(*args, **kwargs)
    return base_func
\$\endgroup\$
2
  • \$\begingroup\$ I couldn't understand what you meant by "This way you don't need to implement it, unless you plan on using it.". Implement what? \$\endgroup\$ Apr 14, 2014 at 6:35
  • \$\begingroup\$ @YatharthROCK if you wrap a base function it will only raise an error when that function is actually called. so if you dont ever use that function with whatever subclass you make, you wont get an error. \$\endgroup\$
    – flakes
    Apr 14, 2014 at 11:29
1
\$\begingroup\$

Source: Question answered while rubber-ducking in the chat-room, LOL ;)

When the Matrix methods try to look for the non-existent methods; they'll error out — naturally — as a NameError.

Note Although there are other great, detailed answers here about idiomatic use of abstract classes, what I eventually went with was just removing the methods; so I think I should accept my answer as per the FAQ and +1 the rest of the useful ones.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.