Is it possible to use Python's constrained TypeVar to make subclasses incompatible?

Viewed 39

According to the docs, constrained TypeVars should match exactly.

However, when using custom classes, the behaviour seems counterintuitive:

from dataclasses import dataclass
from typing import Generic, TypeVar

@dataclass
class PrinterConnection:
    name: str = ""


@dataclass
class WiFiPrinterConnection(PrinterConnection):
    name = "WiFi"


@dataclass
class USBPrinterConnection(PrinterConnection):
    name = "USB"


@dataclass
class USBTypeCPrinterConnection(USBPrinterConnection):
    name = "USB-C"


Connection = TypeVar("Connection", USBPrinterConnection, WiFiPrinterConnection)

@dataclass
class Printer(Generic[Connection]):
    connection: Connection

Printer(WiFiPrinterConnection())      # No Warning - As Expected
Printer(USBPrinterConnection())       # No Warning - As Expected
Printer(USBTypeCPrinterConnection())  # No Warning - NOT Expected

In this example, USBTypeCPrinterConnection is not one of the types defined in the the TypeVar and yet no warning is thrown.

My questions are:

  1. Why isn't a Warning being thrown here?
  2. Is there a way to allow for particular classes and not its subclasses?

These examples were done with Python 3.10 and Pylance for the type checking.

1 Answers

A constrained TypeVar must resolve to one of the specified types, and a subclass can resolve to its parent.

consider

from typing import TypeVar

class Parent:
    pass

class Child(Parent):
    pass

T = TypeVar("T", Parent, int)  # We need at least two arguments
def foo(x: T) -> T: return x

reveal_type(foo(Child()))  # Parent

Playground

as you can see even tho we passed a Child the type var was resolved to a Parent.

Related