1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

[Python] Is it possible to define a TypeVar from Generic as an upper bound for another TypeVar?

Discussão em 'Python' iniciado por Stack, Setembro 11, 2025.

  1. Stack

    Stack Membro Participativo

    Let's assume we have the following class hierarchy:

    class A:
    def a_method(self): ...


    class B(A):
    def b_method(self): ...


    class C(B):
    def c_method(self): ...


    And a generic class:

    from typing import TypeVar, Generic

    T = TypeVar('T')

    class MyClass(Generic[T]):
    def method(self, obj: T) -> T:
    # do something
    return obj


    I want to type the .method so that it returns exactly the same type as the object it accepts, but restricted by the class’s generic type (i.e., an upper bound):

    instance: MyClass[A] = MyClass()

    instance.method(A()) # -> A
    instance.method(B()) # -> B
    instance.method(C()) # -> C

    instance.method(123) # -> Error: int is not a subtype of A


    With the current implementation, static type checkers assume the return type is always the generic type T:

    instance: MyClass[A] = MyClass()

    instance.method(A()) # -> A
    instance.method(B()) # -> A
    instance.method(C()) # -> A


    So the question is: can we type a method so that it returns the same type as its argument, while still enforcing the class’s generic upper bound?

    A more detailed example from my use case

    I have a generic class for a callable registry:

    from typing import TypeVar, Generic, Protocol, Any

    class AnyCallable(Protocol):
    def __call__(self, *__args: Any, **__kwargs: Any) -> Any: ...

    CallableSignaturesT = TypeVar('CallableSignaturesT', bound=AnyCallable, default=AnyCallable)

    class Registry(Generic[CallableSignaturesT]):
    def __init__(self) -> None:
    self._registry: list[CallableSignaturesT] = []

    def __call__(self, func: CallableSignaturesT) -> CallableSignaturesT:
    self._registry.append(func)
    return func


    Now, let's define a more specific callable protocol:

    class MyCallable(Protocol):
    def __call__(self, __arg1: int, __arg2: str, *__args: Any, **__kwargs: Any) -> Any: ...


    And create a registry for it:

    my_registry: Registry[MyCallable] = Registry()


    This registry requires that every callable must accept at least two arguments: int and str. Users can also define additional parameters (which will be provided by the application based on the parameter name):

    @my_registry
    def my_callable(arg1: int, arg2: str, some_arg_from_workflow_dict: bool):
    ...


    Currently, Mypy accepts this function.

    However, when calling my_callable elsewhere, the type checker sees it only as MyCallable, which leads to issues:

    1. No type hints for the user-defined extra parameters.
    2. Mypy assumes first arguments are positional, so you cannot do:

    my_callable(arg1=1, arg2='str', some_arg_from_workflow_dict=True)


    —it only allows:

    my_callable(1, 'str', some_arg_from_workflow_dict=True)


    I tried defining a second TypeVar inside the class to constrain the argument type:

    class Registry(Generic[CallableSignaturesT]):
    def __init__(self) -> None:
    self._registry: list[CallableSignaturesT] = []

    T = TypeVar('T', bound=CallableSignaturesT)

    def __call__(self, func: T) -> T:
    self._registry.append(func)
    return func


    Mypy considers the code valid. But now it rejects any argument for __call__:

    @my_registry
    def my_callable(arg1: int, arg2: str, some_arg_from_workflow_dict: bool):
    ...

    # mypy: Value of type variable "T" of "__call__" of "Registry" cannot be
    # "Callable[[int, str, bool], Any]"


    def my_callable(arg1: int, arg2: str, some_arg_from_workflow_dict: bool) -> Any:
    ...

    my_registry(my_callable)

    # mypy: Value of type variable "T" of "__call__" of "Registry" cannot be
    # "Callable[[int, str, bool], Any]"
    # PyCharm: Expected type 'T ≤: CallableSignaturesT ≤: AnyCallable',
    # got '(arg1: int, arg2: str, some_arg_from_workflow_dict: bool) -> Any' instead

    Continue reading...

Compartilhe esta Página