To illustrate the problem, I'm defining a PositiveNumber class that is a subclass of a Number class. As Python doesn't support type casting, I'm defining the from_number as a convenience method I can use to cast one instance to another. This method is common to all subtypes of the Number class so I'm putting it into the parent class.
from __future__ import annotations
from typing import Any, Dict
class Number:
def __init__(self, value: float, *args, **kwargs) -> None:
self.value = value
@classmethod
def from_number(cls, other: Number) -> Number:
return cls(**other.dict())
def dict(self) -> Dict[str, Any]:
return {
"value": self.value
}
class PositiveNumber(Number):
def __init__(self, value: float, *args, **kwargs) -> None:
if value <= 0:
raise ValueError("Value must be a positive float value.")
super().__init__(value)
@property
def is_positive(self) -> bool:
return True
x = Number(1001) # this could be a positive number
# instead of writing this
y = PositiveNumber(value=x.value)
# I would like to be able to do this
y = PositiveNumber.from_number(x)
type(y)
# >> <class '__main__.PositiveNumber'>
As we can see, y is an instance of PositiveNumber as expected, but it's annotated as an instance of Number.
To avoid this, I would have to overwrite from_number in each subclass to correct the output annotation.
class PositiveNumber(Number):
def __init__(self, value: float, *args, **kwargs) -> None:
if value <= 0:
raise ValueError("Value must be a positive float value.")
super().__init__(value)
@classmethod
def from_number(cls, other: Number) -> PositiveNumber: #overridden
return super().from_number(other)
@property
def is_positive(self) -> bool:
return True
My thinking is that from_number should be an interface method instead, but then I will have to implement the same functionality in every single subtype, which will be tedious because the code will mostly be the same.
Is there a more elegant way of doing this or is this a completely bad design?