DevToolBoxGRATIS
Blogg

Python Type Hints Komplett Guide: mypy, Protocols og Runtime Validering

13 minby DevToolBox

Python type hints (introduced in PEP 484, Python 3.5+) allow you to annotate your code with type information. They do not affect runtime behavior — Python remains dynamically typed — but enable static analysis tools like mypy, pyright, and IDE autocompletion to catch bugs before you run your code. In 2026, type hints are standard practice for any serious Python project.

Basic Type Annotations

Type annotations use a colon after variable or parameter names, and -> for return types. Python 3.9+ allows using built-in generics directly (list[str] instead of List[str]).

# Python type hints — basic built-in types
def greet(name: str) -> str:
    return f"Hello, {name}!"

def add(x: int, y: int) -> int:
    return x + y

def ratio(total: int, count: int) -> float:
    return total / count

def is_valid(value: str) -> bool:
    return len(value) > 0

# Variables can be annotated too
user_id: int = 42
username: str = "alice"
scores: list[int] = [95, 87, 92]

Collections and Optional Types

The typing module provides generic versions of collections and special forms for nullable and union types.

from typing import Optional, Union, Any

# Python 3.9+: use built-in generics directly
names: list[str] = ["Alice", "Bob"]
counts: dict[str, int] = {"apples": 3, "bananas": 5}
coords: tuple[float, float] = (51.5, -0.1)
unique_ids: set[int] = {1, 2, 3}

# Optional: value may be None
def find_user(user_id: int) -> Optional[str]:
    db = {1: "Alice", 2: "Bob"}
    return db.get(user_id)  # may return None

# Union: multiple possible types (Python 3.10+: use X | Y)
def process(value: Union[int, str]) -> str:
    return str(value)

# Python 3.10+ syntax
def process_new(value: int | str) -> str:
    return str(value)

# Any: opt out of type checking
def legacy(data: Any) -> Any:
    return data

Advanced typing Module Features

The typing module offers powerful constructs for describing complex type relationships: Callable, TypeVar, Generic classes, Literal, Final, and TypedDict.

from typing import (
    Callable, Iterator, Generator,
    TypeVar, Generic, Protocol,
    TypedDict, Literal, Final,
    overload, cast, TYPE_CHECKING
)

# Callable: function signatures
Handler = Callable[[str, int], bool]

def apply(func: Callable[[int], int], value: int) -> int:
    return func(value)

# TypeVar: generic type parameters
T = TypeVar('T')
K = TypeVar('K')
V = TypeVar('V')

def first(items: list[T]) -> T:
    return items[0]

# Generic class
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

    def is_empty(self) -> bool:
        return len(self._items) == 0

# Usage
stack: Stack[int] = Stack()
stack.push(1)
stack.push(2)
result: int = stack.pop()  # mypy knows this is int

# Literal: restrict to specific values
def set_direction(direction: Literal["left", "right", "up", "down"]) -> None:
    print(f"Moving {direction}")

# Final: constant that cannot be reassigned
MAX_SIZE: Final = 100

# TypedDict: typed dictionaries
class UserDict(TypedDict):
    name: str
    age: int
    email: str

def process_user(user: UserDict) -> str:
    return f"{user['name']} ({user['age']})"

Protocol: Structural Subtyping

Protocol enables duck typing with type safety. Instead of requiring inheritance, Protocol checks that an object has the required methods and attributes.

from typing import Protocol, runtime_checkable

# Protocol: structural subtyping (duck typing with types)
@runtime_checkable
class Drawable(Protocol):
    def draw(self) -> None: ...
    def get_color(self) -> str: ...

class Circle:
    def draw(self) -> None:
        print("Drawing circle")
    def get_color(self) -> str:
        return "red"

class Square:
    def draw(self) -> None:
        print("Drawing square")
    def get_color(self) -> str:
        return "blue"

def render(shape: Drawable) -> None:
    print(f"Color: {shape.get_color()}")
    shape.draw()

# Both work without inheriting from Drawable
render(Circle())
render(Square())

# Runtime check (requires @runtime_checkable)
print(isinstance(Circle(), Drawable))  # True

dataclasses with Type Hints

dataclasses combine perfectly with type hints, providing auto-generated __init__, __repr__, and __eq__ based on annotated fields.

from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class Point:
    x: float
    y: float

    def distance_to(self, other: 'Point') -> float:
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

@dataclass
class Employee:
    name: str
    department: str
    salary: float
    skills: list[str] = field(default_factory=list)
    _id_counter: ClassVar[int] = 0

    def __post_init__(self) -> None:
        Employee._id_counter += 1
        self.employee_id: int = Employee._id_counter

    def add_skill(self, skill: str) -> None:
        self.skills.append(skill)

# Frozen dataclass (immutable)
@dataclass(frozen=True)
class Color:
    r: int
    g: int
    b: int

    def to_hex(self) -> str:
        return f"#{self.r:02x}{self.g:02x}{self.b:02x}"

red = Color(255, 0, 0)
print(red.to_hex())  # #ff0000

Running mypy and Configuration

mypy is the most popular static type checker for Python. Install it with pip install mypy, then run it on your code.

# Install and run mypy
pip install mypy
mypy src/

# With strict mode (catches more issues)
mypy --strict src/

# Check a single file
mypy mymodule.py

mypy.ini Configuration

# mypy.ini
[mypy]
python_version = 3.12
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True
disallow_any_generics = True
check_untyped_defs = True
strict_optional = True
no_implicit_optional = True

# Ignore missing stubs for third-party packages
[mypy-requests.*]
ignore_missing_imports = True

[mypy-numpy.*]
ignore_missing_imports = True

Type Hint Syntax: Python Version Comparison

FeaturePython 3.8Python 3.9Python 3.10Python 3.12
Basic annotationstyping.List, Dictlist, dictlist, dictlist, dict
Union typesUnion[X, Y]Union[X, Y]X | YX | Y
OptionalOptional[X]Optional[X]X | NoneX | None
TypeAliasMyType = ...MyType = ...TypeAliastype MyType = ...
ParamSpecN/AN/AParamSpecParamSpec
Self typeN/AN/AN/ASelf

Best Practices

  • Start with return types and public API parameters — they give the most value for the least effort.
  • Use Optional[X] or X | None (Python 3.10+) whenever a value can be None. Never use Any unless interfacing with truly dynamic code.
  • Run mypy in CI with --strict or at least --disallow-untyped-defs to prevent type annotation regression.
  • Use Protocol over ABC for structural typing — it works without the class hierarchy.
  • Use TypedDict for dictionary structures, dataclasses for data objects, NamedTuple for lightweight immutable records.

Frequently Asked Questions

Do Python type hints slow down my code?

No. Type hints are completely ignored at runtime (unless you use typing.get_type_hints() explicitly). They add zero runtime overhead. The Python interpreter processes them as expressions that evaluate to nothing relevant.

What is the difference between mypy and pyright?

mypy is the original Python type checker, developed by Guido van Rossum's team. pyright is Microsoft's type checker, used in VS Code (Pylance). pyright is generally faster and stricter. Both support the same PEP standards. For CI, mypy is more configurable. For IDE integration, pyright (via Pylance) is excellent.

When should I use TypeVar vs Protocol?

Use TypeVar when you need generic functions or classes where the type is preserved (e.g., a function that returns the same type it receives). Use Protocol when you want to type-check that an object has certain methods/attributes, regardless of its class hierarchy.

How do I type hint *args and **kwargs?

Use *args: int for positional variadic arguments (all must be the same type), and **kwargs: str for keyword variadic arguments. For mixed types, use *args: Any. Python 3.11+ introduces TypeVarTuple for variadic generics.

Should I add type hints to existing Python code?

Yes, incrementally. Start with public functions and class methods. Use "# type: ignore" sparingly to suppress errors in complex third-party integrations. Enable mypy on new files first, then gradually expand coverage. The benefit compounds over time as the codebase grows.

Related Tools

𝕏 Twitterin LinkedIn
Var dette nyttig?

Hold deg oppdatert

Få ukentlige dev-tips og nye verktøy.

Ingen spam. Avslutt når som helst.

Try These Related Tools

{ }JSON Formatter±Text Diff Checker

Related Articles

Zod Valideringsguide: Skjemaer, Transformasjoner, Refinements og tRPC

Mestre Zod skjemavalidering i TypeScript: skjemaer, transformasjoner, refinements og tRPC.

TypeScript 5 Nye Funksjoner: Dekoratorer, Const Type-parametere og Satisfies

Komplett guide til TypeScript 5 nyheter: dekoratorer, const type params og satisfies.

Git-branching-strategier: GitFlow vs Trunk-Based vs GitHub Flow

Sammenlign GitFlow, Trunk-Based Development og GitHub Flow. Branch-strukturer, merge-arbeidsflyter, CI/CD-integrasjon og aa velge riktig strategi for teamet ditt.