'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?



Solution 1:[1]

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Generic,
    TypeVar,
)

B = TypeVar('B')
A = TypeVar('A')


class MyList(Generic[A]):
    def __init__(*o: A):
        ...

    def extended_by(self, x: B) -> MyList[B]:
        ...


class Y:
    ...


class X:
    ...


ys = MyList(Y(), Y())
xs = ys.extended_by(X())
if TYPE_CHECKING:
    reveal_locals()

This produces :

test.py:32: note: Revealed local types are:
test.py:32: note:     xs: test.MyList[test.X*]
test.py:32: note:     ys: test.MyList[test.Y*]

I didn’t understand the link between A and B. Could you give an example with for instance class Y and X so I can update my answer?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cyrille Pontvieux