We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 8 of 24
Variance in generic protocols
Getting variance wrong on a generic protocol produces errors at every call site, all of which look like the caller’s fault. The caller then “fixes” them with a cast, and the design defect propagates.
The rule, in one line
Variance is determined by where the type parameter appears in the members:
-
only in input positions (parameters) → contravariant. A
Serializer[object]can be used wherever aSerializer[int]is required: something that can serialise anything can certainly serialise an int. -
only in output positions (returns) → covariant. A
Parser[int]can be used wherever aParser[object]is required. - both → invariant.
A de facto covariant protocol cannot be declared invariant — that would break transitivity of assignability, and checkers reject it.
Why the starter fails
The starter uses legacy TypeVar("T"), which is invariant unless you say
otherwise. And you cannot fix it with TypeVar("T", infer_variance=True):
CPython has accepted that argument since 3.12, but mypy 2.3 rejects it.
Under mypy the only route to inferred variance is PEP 695 syntax:
class Serializer[T](Protocol):
def dump(self, value: T) -> str: ...
Declaring TypeVar("T", contravariant=True) and TypeVar("T_co", covariant=True) by hand also works, and is what you will meet in older
code — but it needs two type variables where PEP 695 needs none.
Your task
Rewrite Serializer and Parser so that:
serializer: Serializer[object] = ReprSerializer()
int_parser: Parser[int] = IntParser()
bool_parser: Parser[bool] = BoolParser()
roundtrip(n, serializer, int_parser) # Serializer[object] where [int] wanted
parse_all(int_parser, raws) # Parser[int] where [object] wanted
both type-check. roundtrip[T](value: T, serializer: Serializer[T], parser: Parser[T]) -> T is parser.load(serializer.dump(value)); parse_all(parser: Parser[object], raws: list[str]) -> list[str] returns repr of each parsed
value.
def solve(ints: list[int], flags: list[bool], raws: list[str]) -> list[str]:
returns, in order: repr(roundtrip(...)) for each int, then for each flag,
then parse_all(int_parser, raws).
A 3.15 footnote
Python 3.15 fixed the runtime parameter ordering for
class B(A[T2], Protocol[T1, T2]) and made an unlisted type variable a
TypeError rather than a silent mis-binding. If you write generic protocols
with multiple parameters and mixed bases, that is the release where the
runtime finally agrees with the checkers.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.