73

Suppose you want to constrain a type variable to implement a certain interface. You might write something like so:

from typing import TypeVar, Callable

T = TypeVar('T', Callable)

class Foo(Generic[T]):
    ...

>> TypeError: A single constraint is not allowed

Why is Python unhappy about this use of type constraints? PEP 484 and the Python source code are unhelpful in this regard.

Note: in my particular case I am interested in constraining a type variable to implement an abstract base class, but the principle is the same.

alcorn
  • 1,268
  • 1
  • 8
  • 17

1 Answers1

88

You’re looking for bound:

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

From the docs:

a type variable may specify an upper bound using bound=<type>. This means that an actual type substituted (explicitly or implicitly) for the type variable must be a subclass of the boundary type, see PEP 484.

TypeVar(name, *args) means that the type has to be one of args, so all instances of T would just be replaceable by Callable if T = TypeVar('T', Callable) were allowed.

You should be able to see the difference here (though I didn’t actually try it, heh):

from typing import Generic, TypeVar, Callable

T = TypeVar('T', Callable, bool)

class Foo(Generic[T]):
    value: T

    def __init__(self, value: T) -> None:
        self.value = value

class Bar:
    baz = 5

    def __call__(self):
        pass

f = Foo(Bar())
print(f.value.baz)  # doesn’t typecheck because f.value is only a Callable
Ry-
  • 218,210
  • 55
  • 464
  • 476
  • Minor follow-up: do you have any idea how to access the constructor of the type bound to the type variable? Specifically, I want to call `T()` from within the generic class's `__init__`. – alcorn May 05 '18 at 05:14
  • 1
    @alcorn: That’s not a typing-level operation, so I’m pretty sure it’s not possible/shouldn’t be done. You can have the consumer pass in the class, though, through a parameter of type `Type[T]`. – Ry- May 05 '18 at 05:25
  • 1
    Ok I see, I'm falling into the trap of thinking about Python's type parameters as being comparable to the same feature in languages like C++/Java/C#, but in reality they are (at present) only useful for enforcing type constraints at run time: they can not stand in for actual values. – alcorn May 05 '18 at 18:18
  • 1
    @alcorn a quick search gives https://stackoverflow.com/q/71113281/688080, https://stackoverflow.com/q/67757839/688080, and https://stackoverflow.com/q/69310382/688080. – Ziyuan Jan 07 '23 at 09:14