Further down in the mypy documentation you posted there are two different ways presented. Both are not ideal.
First way: use a type bound
T = TypeVar('T', bound=Product)
Now the generic parameter can only be a Product
order1 = Order[int](primary_product=1, secondary_product=2)
# error: Value of type variable "T" of "Order" cannot be "int"
order1 = Order(primary_product=1, secondary_product=2)
# error: Value of type variable "T" of "Order" cannot be "int"
Unfortunately the generic parameter can now be infered to be exactly Product
:
product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")
order = Order(primary_product=product1, secondary_product=product2) # no error
reveal_type(order)
# Revealed type is 'Order[Product*]'
So you have to specify the generic type
Second way: value restriction
T = TypeVar('T', Hammer, Wrench)
Now even this is correctly identified as an error
product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")
order = Order(primary_product=product1, secondary_product=product2)
# error: Value of type variable "T" of "Order" cannot be "Product"
The problem with this approach is obvious: You have to type all the sub classes of Product
into the TypeVar
constructor.
Third way: factory function
After some experimenting i found a third way that has some odd syntax but combines the advantages of the first two ways. The idea is to force the generic parameter to the type of the first argument.
T = TypeVar('T', bound=Product)
def make_order(primary: P) -> Callable[[P], Order[P]]:
def inner(secondary: P) -> Order[P]:
return Order(primary, secondary)
return inner
make_order(1)(2)
# error: Value of type variable "T" of "make_order" cannot be "int"
product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")
make_order(product1)(product2)
# Argument 1 has incompatible type "Hammer"; expected "Wrench"
order = make_order(product1)(product1)
reveal_type(order)
# Revealed type is 'Order[Wrench*]'
The downsides are:
- strange syntax
- misleading error message ("Argument 1", because it refers to the second call)
To prevent users from directly instantiating Order
you could rename the class to _Order
.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…