python: abstract base class' __init__(): initializion or validation?

max picture max · Feb 27, 2011 · Viewed 15k times · Source

class ABC is an "abstract base class". class X is its subclass.

There's some work that needs to be done in any subclass of ABC, which is easy to forget or do incorrectly. I'd like ABC.__init__() to help catch such mistakes by either:

(1) starting that work, or (2) validating it

This impacts whether super().__init__() is called at the start or at the end of X.__init__().

Here's a simplified example for illustration purposes:

Suppose every subclass of ABC must have an attribute registry, and it must be a list. ABC.__init__() can either (1) initialize registry or (2) check that it was properly created. Following is the sample code for each approach.

Approach 1: initialize in ABC

class ABC:
    def __init__(self):
        self.registry = []

class X:
    def __init__(self):
        super().__init__()
        # populate self.registry here
        ...

Approach 2: validate in ABC

class ABC:
    class InitializationFailure(Exception):
        pass
    def __init__(self):
        try:
            if not isinstance(self.registry, list):
                raise ABC.InitializationError()
        except AttributeError:
            raise ABC.InitializationError()

class X:
    def __init__(self):
        self.registry = []
        # populate self.registry here
        ...
        super().__init__()

Which is a better design?

Answer

John Steven picture John Steven · Feb 27, 2011

Certainly, one prefers approach 1 to approach 2 (as approach 2 relegates the base to a tag interface rather than fulfilling abstract functionality). But, approach 1 doesn't, by itself, meet your goal of preventing the subtype developer from forgetting to implement the super() call properly, ensuring initialization.

you may want to look into the "Factory" pattern to alleviate the possibility of subtype implementers forgetting initialization. Consider:

class AbstractClass(object):
    '''Abstract base class template, implementing factory pattern through 
       use of the __new__() initializer. Factory method supports trivial, 
       argumented, & keyword argument constructors of arbitrary length.'''

   __slots__ = ["baseProperty"]
   '''Slots define [template] abstract class attributes. No instance
       __dict__ will be present unless subclasses create it through 
       implicit attribute definition in __init__() '''

   def __new__(cls, *args, **kwargs):
       '''Factory method for base/subtype creation. Simply creates an
       (new-style class) object instance and sets a base property. '''
       instance = object.__new__(cls)

       instance.baseProperty = "Thingee"
       return instance

This base class can be extended more trivially than in approach 1, using only three (3) lines of code san-commment, as follows:

class Sub(AbstractClass):
   '''Subtype template implements AbstractClass base type and adds
      its own 'foo' attribute. Note (though poor style, that __slots__
      and __dict__ style attributes may be mixed.'''

   def __init__(self):
       '''Subtype initializer. Sets 'foo' attribute. '''
       self.foo = "bar"

Note that though we didn't call the super-class' constructor, the baseProperty will be initialized:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from TestFactory import *
>>> s = Sub()
>>> s.foo
'bar'
>>> s.baseProperty
'Thingee'
>>> 

As its comment indicates, the base class AbstractClass need not use slots, it could just as easily 'implicitly' define attributes by setting them in its new() initializer. For instance:

instance.otherBaseProperty = "Thingee2"

would work fine. Also note that the base class' initializer supports trivial (no-arg) initializers in its subtypes, as well as variable-length arugmented and keyword argument initializers. I recommend always using this form as it doesn't impose syntax in the simplest (trivial constructor) case but allows for the more complex functionality without imposing maintenance.