How to handle subclass that limits arguments of superclass

[How]Should I document constructor arguments that are essentially removed/constant in subclasses? e.g. I have a class like

class Foo:
    def __init__(self, bar: bool):
        self.bar = bar

There are also a variety of subclasses, e.g. MoreSpecificFoo that require bar to be False. If I understand Liskov substition, type variance, PEP 483, and the principle of least surprisal correctly, I ought to keep the bar param in the constructor, add guard code when it’s set to the wrong value, and document that the arg must be false (default) but is retained for supertype compatibility.

That seems like a lot of unneccessary code, when all I really want is to just set self.bar = False in the constructor. Especially if there are different types of more specific Foos that all require bar=False. Are all those extra lines really best practice?

I personally would call super().__init__(False) or super().__init__(bar=False) and not have the subclass’s constructor accept the argument at all. Practicality beats purity.

3 Likes

Constructors aren’t, in the literature I’ve read, normally considered part of the interface (i.e. expected to heed LSP). After all, many class designs treat the constructor as being for internal use only and expect the client to use some kind of factory. (This is useful for classes that need to be able to make modified copies of themselves, but can reach states that aren’t easily expressible in terms of the constructor arguments.)

Speaking of: the enterprise solution here - assuming that bar is supposed to be immutable (if not, why does its value at construction matter?) - is to have FooWithTrueBar and FooWithFalseBar direct subclasses, give the base class an abstract factory instead (perhaps using __new__), inherit MoreSpecificFoo from FooWithFalseBar, and eliminate the bar attribute. (Classes that previously could use either value of bar become split into separate true and false versions, of course.) :wink:

2 Likes