We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Orientation and the Gate step 8 of 13
Placement 1/6: generics and variance
Placement diagnostic, 1 of 6. These six short problems exist to find out what you already know so the course can skip what you do not need. Aim for about seven minutes each. There is no partial credit and no worked example — if a problem is opaque, that is the signal, and its track is where you start.
This one is about variance: the rule that decides whether a Frame[int]
may be handed to a function that asked for a Frame[object].
The starter has a PEP 695 generic class and a caller that will not compile:
error: Argument 1 to "summarise" has incompatible type "Frame[int]";
expected "Frame[object]" [arg-type]
Every int is an object, so the rejection feels wrong. It is not.
Under PEP 695 you do not write covariant=True anywhere; mypy infers the
variance of T from how the class body uses it, and it infers invariant
the moment T appears anywhere writable. An invariant Frame[int] is not a
subtype of Frame[object], and the reason is a real bug the checker is
preventing: if it were allowed, summarise — holding what it believes is a
Frame[object] — could push a str into your Frame[int].
Fix the class so Frame is covariant in T, without touching summarise or
solve.
def solve(ints: list[int], names: list[str]) -> list[str]:
It builds a Frame[int] and a Frame[str], passes each to summarise, and
concatenates the results. summarise renders each row as f"{i}:{row!r}"
with i restarting from 0 for each frame — note !r, so the string "0"
renders as 0:'0' and the integer 0 renders as 0:0.
The part that catches people
Deleting append is necessary but not sufficient. self._rows: list[T]
is still an invariant position all by itself, because list is itself
invariant in its element type — a list[int] is not a list[object] for the
same reason a Frame[int] is not a Frame[object]. Variance is inferred
from the declared types of the attributes, not from whether you happen to
mutate them, and the leading underscore buys you nothing: mypy has no notion
of “private”.
To be covariant, T must only ever appear in positions you can read out of:
return types, and attributes whose own type is immutable in T.
Production consequence
This is the entire reason your codebase is full of Sequence[Foo] and
Mapping[str, Foo] parameters instead of list[Foo] and dict[str, Foo].
Sequence is covariant, so a caller with a list[Dog] can satisfy a
Sequence[Animal] parameter. list is invariant, so the same caller cannot
satisfy a list[Animal] parameter and has to build a copy — or, far more
often, someone reaches for cast and the type system stops being evidence.
Choosing an immutable internal representation is a design decision that
buys you a wider public interface.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.