Is there some way of expressing this Scala code with Python's type hints?
trait List[A] {
def ::[B >: A](x: B): List[B]
}
I'm trying to achieve this sort of thing
class X: pass
class Y(X): pass
class Z(X): pass
xs = MyList(X(), X()) # inferred as MyList[X]
ys = MyList(Y(), Y()) # inferred as MyList[Y]
_ = xs.extended_by(X()) # inferred as MyList[X]
_ = xs.extended_by(Y()) # inferred as MyList[X]
_ = ys.extended_by(X()) # inferred as MyList[X]
_ = ys.extended_by(Y()) # inferred as MyList[Y]
_ = ys.extended_by(Z()) # inferred as MyList[X]
Note that the type MyList is initialised with, and the type it's extended_by, can be anything. MyList is immutable. See the comments for more detail.
What I tried
from __future__ import annotations
from typing import TypeVar, Generic
B = TypeVar('B')
A = TypeVar('A', bound=B)
class MyList(Generic[A]):
def __init__(*o: A):
...
def extended_by(self, x: B) -> MyList[B]:
...
but I get (where the above is in main.py)
main.py:5: error: Type variable "main.B" is unbound
main.py:5: note: (Hint: Use "Generic[B]" or "Protocol[B]" base class to bind "B" inside a class)
main.py:5: note: (Hint: Use "B" in function signature to bind "B" inside a function)
Afaict, it's not allowed to bound on a TypeVar. Is there a workaround in this scenario?
You're trying to specify that B is a supertype of A. But instead of specifying that B should be a supertype of A, it is much easier to state that B is any type, and then the Union A|B is the supertype of A you need.
from typing import TypeVar, Generic
A = TypeVar("A", covariant=True)
B = TypeVar("B")
class MyList(Generic[A]):
def __init__(*objects: A) -> None: ...
def extended_by(self, other: B) -> MyList[A | B]: ...
class X: pass
class Y(X): pass
class Z(X): pass
xs = MyList(X(), X()) # inferred as MyList[X]
ys = MyList(Y(), Y()) # inferred as MyList[Y]
reveal_type(xs.extended_by(X())) # inferred as MyList[X]
reveal_type(xs.extended_by(Y())) # inferred as MyList[X]
reveal_type(ys.extended_by(X())) # inferred as MyList[X]
reveal_type(ys.extended_by(Y())) # inferred as MyList[Y]
reveal_type(ys.extended_by(Z())) # inferred as MyList[Y|Z]
# MyList[Y|Z] is a subtype of MyList[X]
If MyList is immutable, its type variable is most likely covariant. That means that
MyList[X] is a subclass of MyList[Y]. I took the liberty to make your generic parameter covariant, which will also make implementing the extended_by method easier.
Note: If you have to use Python<3.10, make sure to replace | with Union.
Note 2: This is the same approach as, for example list.__add__, see
the code in typeshed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With