Skip to main content
This guide covers common issues you may encounter when using mypy and how to resolve them.

No errors reported for obviously wrong code

There are several reasons why mypy might not flag obviously incorrect code:

Unannotated functions

Functions without type annotations are not type-checked:
def foo(a):
    return '(' + a.split() + ')'  # No error!
This gives no error even though a.split() returns a list, not a string. The error is reported once you add annotations:
def foo(a: str) -> str:
    return '(' + a.split() + ')'
# error: Unsupported operand types for + ("str" and "list[str]")
Use --check-untyped-defs to check function bodies even without annotations.

Type Any propagation

If a value has type Any, operations on it also return Any:
def foo(a) -> str:  # 'a' has type Any
    return '(' + a.split() + ')'  # No error! a.split() is Any
The type of a is unknown, so a.split() has type Any, and adding a string to Any is allowed.
Any types can propagate through your program, reducing type safety.

Unannotated __init__

__init__ without annotations can cause Any to leak into instance variables:
class Bad:
    def __init__(self):
        self.value = "asdf"
        1 + "asdf"  # No error!

bad = Bad()
bad.value + 1  # No error!
reveal_type(bad.value)  # Revealed type is "Any"
Fix by explicitly annotating __init__:
class Good:
    def __init__(self) -> None:
        self.value = "asdf"
        1 + "asdf"  # error: Unsupported operand types

Ignored imports

The --ignore-missing-imports flag silently replaces missing modules with Any:
import some_missing_module  # Silently becomes Any

result = some_missing_module.func()  # result is Any, no errors!
Use --no-silence-site-packages to see these errors.

Name or attribute errors

Name not defined

x = 1
print(y)  # error: Name "y" is not defined
This usually indicates a typo or logic error.

Attribute not found

class MyClass:
    pass

obj = MyClass()
obj.value = 1  # error: "MyClass" has no attribute "value"
Declare attributes in __init__ or as class variables:
class MyClass:
    value: int  # Class variable

    def __init__(self) -> None:
        self.value = 1  # Instance variable

Type incompatibility

Assignment type mismatch

x: int = "hello"  # error: Incompatible types in assignment
The variable was declared as int but assigned a str.

Argument type mismatch

def greet(name: str) -> None:
    print(f"Hello, {name}")

greet(123)  # error: Argument 1 has incompatible type "int"; expected "str"

Return type mismatch

def get_number() -> int:
    return "42"  # error: Incompatible return value type

Dealing with None

Optional values

Values that can be None must be typed as Optional:
def find_user(user_id: int) -> Optional[User]:
    # May return None if not found
    return database.get(user_id)

user = find_user(123)
user.name  # error: Item "None" has no attribute "name"
Check for None before using:
user = find_user(123)
if user is not None:
    print(user.name)  # OK
Or use the walrus operator:
if (user := find_user(123)) is not None:
    print(user.name)  # OK

Unexpected None

If mypy thinks a value might be None when you know it isn’t:
from typing import cast

user = cast(User, find_user(123))  # Tell mypy it's not None
Or use assert:
user = find_user(123)
assert user is not None
print(user.name)  # OK

Generic types

Missing type parameters

Generic types need type parameters:
from typing import List

def process(items: List) -> None:  # error: Missing type parameters
    pass

# Fix:
def process(items: List[str]) -> None:
    pass

Wrong type parameters

def sum_numbers(nums: List[str]) -> int:
    return sum(nums)  # error: Argument 1 to "sum" has incompatible type

# Fix:
def sum_numbers(nums: List[int]) -> int:
    return sum(nums)

Variance issues

List invariance

def print_animals(animals: List[Animal]) -> None:
    for animal in animals:
        print(animal.name)

dogs: List[Dog] = [Dog("Rex")]
print_animals(dogs)  # error: Argument has incompatible type
Lists are invariant. Use Sequence for read-only operations:
from typing import Sequence

def print_animals(animals: Sequence[Animal]) -> None:
    for animal in animals:
        print(animal.name)

print_animals(dogs)  # OK

Import cycles

Import cycles can cause issues with type checking:
# a.py
from b import B

class A:
    def use_b(self, b: B) -> None:
        pass

# b.py
from a import A

class B:
    def use_a(self, a: A) -> None:
        pass
Use TYPE_CHECKING and string literals:
# a.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from b import B

class A:
    def use_b(self, b: "B") -> None:
        pass

Stub file issues

Conflicting stub

If you have a custom stub that conflicts with an installed one:
# Remove from MYPYPATH or rename the stub
export MYPYPATH=/path/without/conflicting/stub

Outdated stubs

Stub packages may be outdated:
# Update stub packages
pip install --upgrade types-requests types-redis

Performance issues

Slow type checking

For large codebases:
dmypy start
dmypy run -- myproject
[mypy]
incremental = True
cache_dir = .mypy_cache
mypy --skip-version-check --skip-cache-mtime-checks myproject

Platform-specific issues

Different types on different platforms

Some types differ between platforms:
import sys

if sys.platform == "win32":
    from typing import WindowsPath as PathType
else:
    from typing import PosixPath as PathType
Or use Union:
from pathlib import Path

def process_path(p: Path) -> None:  # Works on all platforms
    pass

Debugging type issues

Use reveal_type()

Debug what mypy thinks a type is:
reveal_type(x)  # Revealed type is "builtins.int"

Use reveal_locals()

See all local variable types:
def foo() -> None:
    x = 1
    y = "hello"
    reveal_locals()
    # Revealed local types are:
    # x: builtins.int
    # y: builtins.str

Enable verbose output

mypy --verbose myproject

Show error context

mypy --show-error-context myproject

Common workarounds

# type: ignore

Suppress specific errors:
x = func()  # type: ignore[attr-defined]

cast()

Tell mypy the correct type:
from typing import cast
x = cast(str, func())

assert

Narrow types:
assert x is not None
print(x.value)

# pragma: no cover

For unreachable code:
if TYPE_CHECKING:  # pragma: no cover
    from module import Class
While workarounds are sometimes necessary, prefer fixing the underlying issue when possible.