Function with multiple generic parameters of same type #1201
-
I want to create a function with two parameters (P1, P2) with the following constraints
An example to demonstrate what I mean :
The reason the second line does not throw an error seems to be that the type of K@food_mixer is assumed to be Union[Rice, Sandwich]
I was hoping for a solution which does not require creating a constraint like |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 5 replies
-
No, you can't accomplish this with bound TypeVars. The constraint solver will find always find a valid solution using either a union ( As you pointed out, you can use a constrained TypeVar to accomplish this, but that requires each subtype to be listed in the TypeVar definition. Here's another variant that does work with a bound TypeVar, but it requires another class. class FoodMixer(Generic[K]):
def __init__(self, f1: K):
self._f1 = f1
def mix(self, f2: K) -> K:
return self._f1.mix(f2)
FoodMixer(Rice(10)).mix(Sandwich(5)) # Type error And here's another variant, but it requires you to explicitly specialize the class when you use it: class FoodMixer(Generic[K]):
@staticmethod
def mix(f1: K, f2: K) -> K:
return f1.mix(f2)
FoodMixer[Rice].mix(Rice(10), Sandwich(4)) # Type error
# A type alias makes this a little nicer.
RiceMixer = FoodMixer[Rice]
RiceMixer.mix(Rice(10), Sandwich(4)) # Type error |
Beta Was this translation helpful? Give feedback.
-
@erictraut Thank you for your alternatives. I tried using the Invariant property of TypeVar to force the constraint checker to disregard unions. from abc import abstractmethod
from typing import Generic, TypeVar
K = TypeVar("K", bound="Food")
class AnyFood(Generic[K]):
@abstractmethod
def unwrap(self) -> K:
pass
class Food:
def __init__(self, quantity: int) -> None:
self._quantity = quantity
super().__init__()
def quantity(self):
return self._quantity
class Rice(Food, AnyFood["Rice"]):
def unwrap(self):
return self
class Sandwich(Food, AnyFood["Sandwich"]):
def unwrap(self):
return self
# does not inherit from Food
class FakeFood(AnyFood["FakeFood"]):
def unwrap(self):
return self
def food_mixer(w1: AnyFood[K], w2: AnyFood[K]) -> K:
f1 = w1.unwrap()
f2 = w2.unwrap()
return type(f1)(f1.quantity() + f2.quantity())
mixed_food1 = food_mixer(Rice(10), Rice(10)) # this line does not show an error
mixed_food2 = food_mixer(Sandwich(10), Sandwich(10)) # this line does not show an error
mixed_food3 = food_mixer(Rice(10), Sandwich(10)) # this line does show an error as expected!!
mixed_food4 = food_mixer(FakeFood(10), FakeFood(10)) # this line does show an error as expected!!
It is giving the expected outcomes in both pyright and mypy. And pylance is able to derive the type of mixed_food1 and mixed_food2 to be Rice and Sandwich respectively. |
Beta Was this translation helpful? Give feedback.
-
FYI I have opened a discussion on the typing forum to see if we could add a solution to this issue. |
Beta Was this translation helpful? Give feedback.
No, you can't accomplish this with bound TypeVars. The constraint solver will find always find a valid solution using either a union (
Sandwich | Rice
) or a join (Food
). Both solutions are valid.As you pointed out, you can use a constrained TypeVar to accomplish this, but that requires each subtype to be listed in the TypeVar definition.
Here's another variant that does work with a bound TypeVar, but it requires another class.
And here's another variant, but it requires you to explicitly special…