Today, I wanted to give a subclass of an Abstract Base Class a more specific behaviour by overriding a method and changing the method interface:
from abc import ABC
class DecisionTree(ABC):
...
def evaluate(
self,
activity: Activity,
params: Params,
) -> industry_models.DecisionPath:
_evaluate_steps(step_map=self.step_map, params=params)
...
class InvoiceDecisionTree(tree.DecisionTree):
...
def evaluate(
self, invoice: models.Invoice
) -> models.DecisionPath:
assert invoice.activity
params = tree.Params(state={"invoice": invoice})
return super().evaluate(activity=invoice.activity, params=params)
However, I got this mypy
error:
error: Signature of "evaluate" incompatible with supertype "DecisionTree" [override]`.
Here is what the mypy docs say about it:
It’s unsafe to override a method with a more specific argument type, as it violates the Liskov substitution principle. For return types, it’s unsafe to override a method with a more general return type.
Instead of ignoring this with # ignore: type[override]
, I had a chat with ChatGPT about different ways to address this issue. I ended up opting for a simple wrapper method for the customized behaviour.
class InvoiceDecisionTree(tree.DecisionTree):
...
def evaluate_for_invoice(
self, invoice: models.Invoice
) -> models.DecisionPath:
assert invoice.activity
params = tree.Params(state={"invoice": invoice})
return self.evaluate(activity=invoice.activity, params=params)