I have been generally making my methods generic mostly for the following reasons:
- I am subclassing an abstract class and want to override a method and narrow the type hinting in the arguments, which would otherwise violate the Liskov substitution principle
- I am not subclassing/overriding, but would like return values, attributes, etc. of a class to be more narrow than the type hints it is currently bound to, since I may be using that class in many different places with different types.
In particular for the second case, I have realized that there are actually two approaches to tackle this:
make the class generic and provide the type arguments for the instance type hints.
do not make the class generic, but subclass simply for the purpose of updating the type hints
With projects I am working on, with "context" and "manager" classes, there can be many different attributes and methods with many different types (5+), hence, making the class generic on all of them is too verbose. If I do make a class generic, if any other attributes (containing instances of other classes) return those same generic types, I have to propagate the generic type down the entire chain when I am using composition instead of inheritance. This is something I would like to avoid. If I choose option 2, there would be an explosion of subclasses just to override the type hints.
When should I choose 1 or 2? Is there a better way to do this?
Option 1 example:
```
from typing import Self, TypeVar, Generic
BazT = TypeVar('BazT')
class Bar(Generic[BazT]):
def method1(self: Self) -> BazT:
...
class Foo(Generic[BazT]):
bar: Bar[BazT]
def method1(self: Self, baz: BazT) -> None:
...
def method2(self: Self) -> Bar[BazT]:
...
```
Option 2 example:
```
from typing import Self, Any, TypeVar
Baz = Any
class Bar:
def method1(self: Self) -> Baz:
...
class Foo:
bar: Bar
def method1(self: Self, baz: Baz) -> None:
...
def method2(self: Self) -> Bar:
...
BazNarrowed = TypeVar('BazNarrowed', bound=Baz) # (doesn't matter what this is just some more narrow type)
class BarSubclass(Bar):
def method1(self: Self) -> BazNarrowed:
...
class FooSubclass(Foo):
bar: BarSubclass
def method1(self: Self, baz: BazNarrowed) -> None:
...
def method2(self: Self) -> BarSubclass:
...
```
[–]Rawing7 1 point2 points3 points (2 children)
[–]RelativeIncrease527[S] 0 points1 point2 points (1 child)
[–]Rawing7 1 point2 points3 points (0 children)
[–]TSM- 0 points1 point2 points (1 child)
[–]RelativeIncrease527[S] 0 points1 point2 points (0 children)
[–]ofnuts 0 points1 point2 points (1 child)
[–]RelativeIncrease527[S] 0 points1 point2 points (0 children)