Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Workaround for TypeVar bound on a TypeVar?

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?

like image 579
joel Avatar asked Nov 17 '25 13:11

joel


1 Answers

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.

like image 62
Dodezv Avatar answered Nov 20 '25 04:11

Dodezv