In the last exercise, mypy rejected code that passed its visible tests. Here are two more errors you will see often:
import json
def load_port(text: str) -> int:
config = json.loads(text)
return config["port"]
def count(items: list) -> int:
return len(items)
L6:5 [no-any-return]
Returning Any from function declared to return "int"
L9:18 [type-arg]
Missing type arguments for generic type "list"
Plain mypy, without --strict, accepts this file. Both errors come from
strict mode.
Strict mode is a list of checks
--strict turns on thirteen flags at once. Each flag adds one check, and
the error code in square brackets tells you which check found the error.
Other errors, such as [union-attr] from the last exercise, come from
mypy's default checks. You see those with or without --strict.
Here are the strict-mode errors you will meet most, with the usual fix for each.
Annotate every function
[no-untyped-def] means a function has no annotation for a parameter or
for its return value. A function that returns nothing still needs -> None:
def log(message: str) -> None:
print(message)
[no-untyped-call] means annotated code calls an untyped function, one
with no annotations. Annotate the function you call, and the error goes
away.
Say what a collection holds
[type-arg] means you wrote list, dict or set without saying what it
holds. Write list[str] or dict[str, int] instead. A bare list can hold
anything, so mypy cannot check what you take out of it.
Check an Any value before you return it
Any is the type mypy gives a value it knows nothing about. mypy allows
every operation on an Any value and checks none of them. json.loads
returns Any, because mypy cannot know what the JSON text holds.
[no-any-return] means a function that promises a real type returns an
Any value instead. Check the value first. After the isinstance check,
mypy knows that port is an int:
import json
def load_port(text: str) -> int:
config = json.loads(text)
port = config["port"]
if not isinstance(port, int):
raise ValueError("port must be an integer")
return port
Compare values that can be equal
[comparison-overlap] means you compared two values that can never be
equal:
def is_owner(owner_id: int, user_id: str) -> bool:
return owner_id == user_id # always False
Python allows the comparison and returns False every time. Convert one
side first, for example with int(user_id). Do it where the value enters
your program.
Delete ignore comments you no longer need
A comment such as # type: ignore[arg-type] hides one kind of error on its
line. If the line has no such error, strict mode reports [unused-ignore].
Delete the comment.
On this site, a # type: ignore with no error code in square brackets fails
the submission, and so does a # mypy: comment. Either one can turn off
mypy for the whole file. Fix the error instead.
Check your understanding
mypy reports [no-any-return] on a function that returns a value from json.loads. Why fix it with a check instead of an ignore comment?
Show answer
Hide answer
The comment hides the error, but the value can still be the wrong type. If
the JSON holds the string "8080", the function returns it while promising
an int, and the bug shows up later, far from its cause. An isinstance
check fails at once, with a clear message, and tells mypy the real type.
What the site adds to strict mode
The site runs mypy with three more options:
mypy --strict --warn-unreachable --extra-checks --ignore-missing-imports
--warn-unreachablereports code that can never run, as[unreachable]. A common cause isif value is None:on a parameter typedstr.--extra-checksis already part of--strict.--ignore-missing-importslets you import a package that has no type information. mypy treats the package asAny.
The site also passes an empty --config-file=, so mypy ignores the
settings in your own configuration files. Every learner's code gets the
same check.
Going further: all thirteen strict flags
This is the list in mypy 2.4.0. It can change between releases, so run
python -m mypy --help to see the list for the version you have.
| Flag | What it reports | Error code |
|---|---|---|
--disallow-untyped-defs |
A function with missing annotations | no-untyped-def |
--disallow-incomplete-defs |
A function with only some annotations | no-untyped-def |
--check-untyped-defs |
Errors inside untyped functions | varies |
--disallow-untyped-calls |
Typed code that calls an untyped function | no-untyped-call |
--disallow-untyped-decorators |
An untyped decorator on a typed function | untyped-decorator |
--disallow-subclassing-any |
A class whose base class is Any |
misc |
--disallow-any-generics |
A generic such as list with no type arguments |
type-arg |
--warn-redundant-casts |
A cast to the type the value already has |
redundant-cast |
--warn-unused-ignores |
A # type: ignore that hides nothing |
unused-ignore |
--warn-return-any |
Returning Any where a real type is promised |
no-any-return |
--no-implicit-reexport |
Importing a name that a module did not re-export | attr-defined |
--strict-equality |
==, != or in on types that can never match |
comparison-overlap |
--extra-checks |
A few rarer checks, such as on TypedDict updates |
varies |
Read more in mypy's command-line options and its list of error codes.