I'm struggling to have the right types in a code implying two related classes and two related mixins, which can be applied on these classes.
Here is the minimal code I have to demonstrate my problem and expectations.
Three things I need help on:
- The last statement
TestCImpl2().mixin_func2()does not return a compatible type with what I'm expecting. Any idea to fix that? - The current code requires to repeat a generic type on the mixin:
class TestCImpl2(MixinOnMyBase2[MyBase2[TestCImpl1]], MyBase2[TestCImpl1]). I'm pretty sure there is a solution which does not involve having to do this. - I currently use a Protocol, but I'm not sure it is required here.
Note: I know I'm over-engineering it, but I take the occasion to go further in Python typing system.
from typing import Protocol, Self
# --- Not in my control ---
# A simple class
class Base1:
pass
# A class which require a generic type
class Base2[T]:
pass
# --- My base classes and mixins ---
# My simple base class with a custom function which return the implementation class type
class MyBase1(Base1):
def base_func1(self) -> Self:
return self
# My generic-type base class with a custom function which return the implementation class type
# I require the generic type to be based on MyBase1 and default to MyBase1 when not specified
# The class has a property of generic-type MyBase1 and a function returning implementation class type
class MyBase2[MyT: MyBase1 = MyBase1](Base2[MyT]):
object1: MyT
def base_func2(self) -> Self:
return self
# A mixin I want sometimes to apply on MyBase1 based classes
class MixinOnMyBase1:
def mixin_func1(self) -> Self:
return self
# A Protocol just to type-hint a generic type with object1 and base_func2,
# I'm not sure it is required
class MixinOnMyBase2Protocol[T](Protocol):
@property
def object1(self) -> MixinOnMyBase1: ...
def base_func2(self, *args, **kwargs) -> T: ...
# A mixin I want to sometimes to apply on MyBase2 based classes, which works by pair with MyBase1
class MixinOnMyBase2[T]:
def mixin_func2(self: MixinOnMyBase2Protocol[T]) -> T:
self.object1.mixin_func1()
return self.base_func2()
# --- My expected final implementations ---
# Test A
class TestAImpl1(MyBase1):
pass
class TestAImpl2(MyBase2):
pass
a: MyBase2 = TestAImpl2().base_func2()
# Test B
class TestBImpl1(MyBase1):
pass
class TestBImpl2(MyBase2[TestBImpl1]):
pass
b: MyBase2[TestBImpl1] = TestBImpl2().base_func2()
c: TestBImpl2 = TestBImpl2().base_func2()
# Test C
class TestCImpl1(MixinOnMyBase1, MyBase1):
pass
# I find ugly to have to repeat MyBase2[TestCImpl1] on the mixin
class TestCImpl2(MixinOnMyBase2[MyBase2[TestCImpl1]], MyBase2[TestCImpl1]):
pass
d: MyBase2[TestCImpl1] = TestCImpl2().mixin_func2()
# Here mypy complain:
# Incompatible types in assignment (expression has type "MyBase2[TestCImpl1]", variable has type "TestCImpl2")
# Mypy assignment
e: TestCImpl2 = TestCImpl2().mixin_func2()