LSP and constructors with first-class classes

John J. Lee jjl at pobox.com
Sun Aug 26 16:37:48 EDT 2001


I've been thinking about this for a while now, and I still can't see what
the 'right thing' is.

I have a class, Fit, that is not intended to be instantiated itself: you
have to subclass it first.  It isn't only an interface, though, it
implements a lot, too.  Quote from docstring:

"""
    Subclasses' __init__ method should call Fit.__init__ with the Scan and
    Data instances as arguments, and then add a Theory subclass as
    self.theory.  Everything else is optional...
"""

The constructor takes two arguments:

    def __init__(self, scan, data):
        """
        scan: must have an attribute 'name'
        data: Data instance
        """
        self.scan, self.data = scan, data
        [...]

Data is unproblematic.  The 'Scan' is what is puzzling me.  It clearly
shouldn't be there: some Fits shouldn't even *have* a Scan.  So I'll get
rid of it:

    def __init__(self, data):

However, instances of most currently existing subclasses *do* need a Scan
instance in order to get themselves into a sensible state.  There are two
ways of making this happen.  First, have the user of a subclass call an
extra method:

class MyFit(Fit):
    def __init__(self, data):
        Fit.__init__(self, data)
        self.data = data

    def set_scan(self, scan):
        self.scan = scan
        [finish initialisation, using scan...]

This would be fine if scan were only needed for new behaiviour, unknown to
Fit, and provided by MyFit, but MyFit needs its Scan just to do the same
stuff that Fit does, so now the instance violates the LSP
(http://www.objectmentor.com/publications/lsp.pdf) because you can't
assume that a newly-constructed MyFit will do the things you expect of
other Fits.

So, second option, change the constructor:

class MyFit(Fit):
[...]
    def __init__(self, data, scan):
        self.data, self.scan = data, scan

Now it satisfies the LSP -- sort of.  It's certainly better than the other
options, and in languages where classes aren't first-class objects, you
would be satisfied.  In Python, however, someone (the future me, for
example) might reasonably expect this to work:

class TheirFitUser(Fit):
    def foo(self, fit_class):
        fit = fit_class(data)
        [...]

This will break on MyFit, of course.

So, is this unavoidable, or have I arguably made a mistake in subclassing
from Fit in this case?  In which case, how should I have organised
things...?

Well, I seem to understand the problem a bit better, having written this,
so by the time somebody answers this, I'll expect to understand the
solution better than they do <0.5 wink>.


John

Anybody (other than Tim Peters) <wink>ing in floating point in this style
has clearly been reading comp.lang.python too much...




More information about the Python-list mailing list