Skip to main content

Instance and class attributes

Mypy detects if you try to access a missing attribute. For this to work correctly, instance and class attributes must be defined or initialized within the class:
class A:
    def __init__(self, x: int) -> None:
        self.x = x  # Aha, attribute 'x' of type 'int'

a = A(1)
a.x = 2  # OK!
a.y = 3  # Error: "A" has no attribute "y"
This is similar to each class having an implicitly defined __slots__ attribute. This is only enforced during type checking, not at runtime.

Explicit attribute annotations

You can declare types of variables in the class body explicitly:
class A:
    x: list[int]  # Declare attribute 'x' of type list[int]

a = A()
a.x = [1]  # OK
You can only define an instance variable within a method if you assign to it explicitly using self:
class A:
    def __init__(self) -> None:
        self.y = 1   # Define 'y'
        a = self
        a.x = 1      # Error: 'x' not defined

Annotating __init__ methods

The __init__ method is special — it doesn’t return a value. This is best expressed as -> None:
class C1:
    def __init__(self) -> None:
        self.var = 42
You can omit the return type if at least one argument is annotated:
class C2:
    def __init__(self, arg: int):
        self.var = arg
If __init__ has no annotated arguments and no return type annotation, it is considered an untyped method and its body is not type checked.

Class attribute annotations

Use ClassVar[T] to explicitly declare that an attribute should not be set on instances:
from typing import ClassVar

class A:
    x: ClassVar[int] = 0  # Class variable only

A.x += 1  # OK

a = A()
a.x = 1  # Error: Cannot assign to class variable "x" via instance
print(a.x)  # OK -- can be read through an instance
An attribute without ClassVar annotation can be used as both a class and instance variable.

Overriding methods

When overriding a statically typed method, mypy checks that the override has a compatible signature:
1

Compatible overrides

class Base:
    def f(self, x: int) -> None:
        ...

class Derived(Base):
    def f(self, x: int) -> None:  # OK
        ...
2

Incompatible parameter types

class Derived(Base):
    def f(self, x: str) -> None:  # Error: type of 'x' incompatible
        ...
3

Accepting more arguments

class Derived(Base):
    def f(self, x: int, y: int = 0) -> None:  # OK
        ...
You can vary return types covariantly and argument types contravariantly in overriding methods.

The @override decorator

You can explicitly mark a method as overriding a base method using @override:
from typing import override

class Base:
    def f(self, x: int) -> None:
        ...

class Derived(Base):
    @override
    def f(self, x: int) -> None:  # OK
        ...
    
    @override
    def g(self, y: str) -> None:  # Error: no corresponding base method found
        ...
Use --enable-error-code explicit-override to require that method overrides use the @override decorator.

Abstract base classes

Mypy supports Python abstract base classes (ABCs). Use ABCMeta metaclass and @abstractmethod decorator:
from abc import ABCMeta, abstractmethod

class Animal(metaclass=ABCMeta):
    @abstractmethod
    def eat(self, food: str) -> None:
        pass
    
    @property
    @abstractmethod
    def can_walk(self) -> bool:
        pass

class Cat(Animal):
    def eat(self, food: str) -> None:
        ...  # Body omitted
    
    @property
    def can_walk(self) -> bool:
        return True

x = Animal()  # Error: 'Animal' is abstract due to 'eat' and 'can_walk'
y = Cat()     # OK
Mypy performs checking for unimplemented abstract methods even if you omit the ABCMeta metaclass.

Implicit abstract classes

A class that inherits an abstract method but doesn’t implement it is implicitly abstract:
from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
    @abstractmethod
    def f(self, x: int) -> None:
        pass

class Derived(Base):  # No error -- Derived is implicitly abstract
    def g(self) -> None:
        ...

d = Derived()  # Error: 'Derived' is abstract

Slots

When a class has explicitly defined __slots__, mypy checks that all attributes assigned to are members of __slots__:
class Album:
    __slots__ = ('name', 'year')
    
    def __init__(self, name: str, year: int) -> None:
        self.name = name
        self.year = year
        self.released = True  # Error: not in "__slots__"
Mypy checks __slots__ when:
  1. All base classes (except builtin ones) have explicit __slots__ defined
  2. __slots__ does not include __dict__
  3. All values in __slots__ are string literals

Best practices

Either initialize them in __init__ or declare them in the class body with type annotations.
This prevents accidental modification through instances and makes your intent clear.
Explicitly marking overridden methods helps catch errors when base methods are renamed.
Abstract base classes make your interfaces explicit and help catch missing implementations.