I occasionally run into a scenario like the following:
from typing import Generic, TypeVar
T = TypeVar('T')
class Widget(Generic[T]):
content: T
class Jibbit(Generic[T]):
element: T
class ThingHolder:
thing: Widget | Jibbit
In the Python standard library, this situation arises in logging.handlers.QueueListener
, where the QueueListener.queue
attribute is equivalent to ThingHolder.thing
above.
Now I want to convert ThingHolder
so that it is parameterized by the type of the thing
that it holds, so that I can differentiate between, for example, ThingHolder[Widget[int]]
and ThingHolder[Jibbit[int]]
.
How do you spell this correctly with a TypeVar
? If I write
Thing = TypeVar('Thing', bound=Widget | Jibbit)
then I get an error because I didn't specify a parameter for the two parameterized types.
It appears that you're supposed to parameterize the types in the bound=
itself, and not attempt to parameterize the new type variable:
Thing = TypeVar('Thing', bound=Widget[Any] | Jibbit[Any])
class ThingHolder(Generic[Thing]):
thing: Thing
def __init__(self, thing: Thing) -> None:
self.thing = thing
I originally thought that this wouldn't work, because the "inner" type parameter isn't written anywhere in the definition. But it does in fact work:
# OK
w_int: Widget[int] = Widget(1)
th_w_int: ThingHolder[Widget[int]] = ThingHolder(w_int)
# OK
w_str: Widget[str] = Widget("hello")
th_w_str: ThingHolder[Widget[str]] = ThingHolder(w_str)
# Errors!
th_w_str = ThingHolder(w_int)
th_w_int = ThingHolder(w_str)
reveal_type(ThingHolder(Jibbit(None)))
# __main__.ThingHolder[__main__.Jibbit[None]]
reveal_type(ThingHolder(Jibbit([1,2,3])))
# __main__.ThingHolder[__main__.Jibbit[builtins.list[builtins.int]]]
I think it works because something like Widget[int]
is indeed a subtype of Widget[Any] | Jibbit[Any]
, while something like list[int]
is not. Clearly, Mypy is smart enough to track the "inner" types, even when they are not explicitly written in the class definition. Moreover, if you had the opportunity to inject your own type variable in the inner parameter, you might accidentally mess it up by using the wrong type variance for the inner parameter.