We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Orientation and the Gate step 3 of 13
Reading a mypy error: codes, reveal_type, assert_type
Every mypy diagnostic ends in a bracketed error code:
solution.py:12:11: error: Argument 1 to "load" has incompatible type
"str"; expected "Path" [arg-type]
That [arg-type] is the whole point of this lesson. It is the difference
between
value = load(path) # type: ignore
which silences every present and future error on that line — including the
None-return bug you introduce next quarter — and
value = load(path) # type: ignore[arg-type]
which silences exactly the one you triaged. Under this course’s gate
--warn-unused-ignores is on, so a scoped ignore that stops being needed
becomes an error in its own right and gets deleted. A blanket ignore never
will. You cannot scope what you cannot name.
Two more instruments, both of which cost you nothing at runtime:
-
reveal_type(x)— a mypy-only pseudo-function. Drop it anywhere, run mypy, and it printsnote: Revealed type is "builtins.str". It is not a real builtin: leave it in and the module raisesNameErrorwhen it actually runs. -
assert_type(x, str)fromtyping— a checked assertion. mypy errors if the inferred type is not exactlystr; at runtime it returns its first argument unchanged. This is how you pin an inference in a test so that a refactor which silently widens something toAnyfails CI instead of passing quietly.
The problem
--strict is not a check. It is thirteen checks bundled behind one flag (the
companion article lists them). So a team’s mypy invocation is a claim about
which checks are on, and in practice half the flags people append to
--strict are already inside it — noise that makes the config unreadable and
hides the two or three flags that are genuinely doing work.
Two module constants are given to you:
-
STRICT_IMPLIES— the thirteen flags--strictturns on. -
BEYOND_STRICT— recognised flags that--strictdoes not turn on.
Write:
def explain_strict(flags: Sequence[str]) -> tuple[frozenset[str], frozenset[str]]:
returning (redundant, additive):
-
redundant — flags in the input that
--strictalready implies. Only possible when--strictis itself in the input; without it nothing is implied, so nothing is redundant. - additive — flags in the input that genuinely add a check.
-
--strictitself belongs to neither set. -
Any flag in neither constant raises
UnknownFlagError(flag), carrying the offending flag on a.flagattribute.
Then the graded entrypoint, which is the boundary — it must not let that exception escape:
def solve(flags: Sequence[str]) -> dict[str, frozenset[str]]:
Return {"redundant": ..., "additive": ..., "unknown": ...}. On an unknown
flag, redundant and additive are empty and unknown holds the single flag
that tripped — the first offender in input order, so the report is stable
regardless of set iteration order.
What the gate exercises
frozenset[str], not bare frozenset. Under --disallow-any-generics — one
of the thirteen — a bare frozenset annotation means frozenset[Any] and is
an error. Final on the constants stops a later edit rebinding them. And the
except UnknownFlagError as exc: branch is where the declaration earns its
keep: mypy knows exc.flag is a str only because you declared it in
__init__. Attach it with a bare exc.flag = flag from the raise site
instead and you get "UnknownFlagError" has no attribute "flag" [attr-defined]
— which is the checker correctly telling you that attributes bolted on from
outside are invisible to every other reader of the class.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.