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?
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.