Claude
Skills
Sign in
Back

python-type-hints

Included with Lifetime
$97 forever

Complete Python type hints system. PROACTIVELY activate for: (1) Built-in generics (list[str], dict[str, int]), (2) Union types (str | None), (3) Type parameter syntax 3.12+, (4) Protocol for structural typing, (5) TypedDict for dict schemas, (6) Literal and Final types, (7) TypeGuard and TypeIs, (8) ParamSpec for decorators, (9) Mypy/Pyright configuration. Provides: Type syntax, Protocol patterns, TypedDict, mypy config. Ensures static type safety with gradual typing strategy.

General

What this skill does


## Quick Reference

| Type | Syntax (3.9+) | Example |
|------|---------------|---------|
| List | `list[str]` | `names: list[str] = []` |
| Dict | `dict[str, int]` | `ages: dict[str, int] = {}` |
| Optional | `str \| None` | `name: str \| None = None` |
| Union | `int \| str` | `value: int \| str` |
| Callable | `Callable[[int], str]` | `func: Callable[[int], str]` |

| Feature | Version | Syntax |
|---------|---------|--------|
| Type params | 3.12+ | `def first[T](items: list[T]) -> T:` |
| type alias | 3.12+ | `type Point = tuple[float, float]` |
| Self | 3.11+ | `def copy(self) -> Self:` |
| TypeIs | 3.13+ | `def is_str(x) -> TypeIs[str]:` |

| Construct | Use Case |
|-----------|----------|
| `Protocol` | Structural subtyping (duck typing) |
| `TypedDict` | Dict with specific keys |
| `Literal["a", "b"]` | Specific values only |
| `Final[str]` | Cannot be reassigned |

## When to Use This Skill

Use for **static type checking**:
- Adding type hints to functions and classes
- Creating typed dictionaries with TypedDict
- Defining protocols for duck typing
- Configuring mypy or pyright
- Writing generic functions and classes

**Related skills:**
- For Python fundamentals: see `python-fundamentals-313`
- For testing: see `python-testing`
- For FastAPI schemas: see `python-fastapi`

---

# Python Type Hints Complete Guide

## Overview

Type hints enable static type checking, better IDE support, and self-documenting code. Python's typing system is gradual - you can add types incrementally.

## Modern Type Hints (Python 3.9+)

### Built-in Generic Types

```python
# Python 3.9+ - Use built-in types directly
# No need for typing.List, typing.Dict, etc.

def process_items(items: list[str]) -> dict[str, int]:
    return {item: len(item) for item in items}

# Collections
names: list[str] = ["Alice", "Bob"]
ages: dict[str, int] = {"Alice": 30, "Bob": 25}
coordinates: tuple[float, float] = (1.0, 2.0)
unique_ids: set[int] = {1, 2, 3}
frozen_data: frozenset[str] = frozenset(["a", "b"])

# Nested generics
matrix: list[list[int]] = [[1, 2], [3, 4]]
config: dict[str, list[str]] = {"servers": ["a", "b"]}
```

### Union Types (Python 3.10+)

```python
# Old way (still works)
from typing import Union, Optional

def old_style(value: Union[int, str]) -> Optional[str]:
    return str(value) if value else None

# New way (Python 3.10+)
def new_style(value: int | str) -> str | None:
    return str(value) if value else None

# Optional is just Union with None
# Optional[str] == str | None
```

### Type Aliases

```python
# Simple type alias
UserId = int
Username = str

def get_user(user_id: UserId) -> Username:
    return "user_" + str(user_id)

# Complex type alias
from typing import TypeAlias

JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | dict[str, "JsonValue"]

# Python 3.12+ type statement
type Point = tuple[float, float]
type Vector[T] = list[T]
type JsonDict = dict[str, "JsonValue"]
```

### Type Parameters (Python 3.12+)

```python
# Old way with TypeVar
from typing import TypeVar

T = TypeVar("T")

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

# New way (Python 3.12+)
def first[T](items: list[T]) -> T:
    return items[0]

# Generic classes
class Stack[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()

# Multiple type parameters
def merge[K, V](d1: dict[K, V], d2: dict[K, V]) -> dict[K, V]:
    return {**d1, **d2}

# Bounded type parameters
from typing import SupportsLessThan

def minimum[T: SupportsLessThan](a: T, b: T) -> T:
    return a if a < b else b

# Default type parameters (Python 3.13+)
class Container[T = int]:
    def __init__(self, value: T) -> None:
        self.value = value
```

## Function Signatures

### Basic Functions

```python
from typing import Callable, Iterable, Iterator

# Simple function
def greet(name: str) -> str:
    return f"Hello, {name}!"

# Multiple parameters
def create_user(name: str, age: int, email: str | None = None) -> dict:
    return {"name": name, "age": age, "email": email}

# *args and **kwargs
def log(*args: str, **kwargs: int) -> None:
    for arg in args:
        print(arg)
    for key, value in kwargs.items():
        print(f"{key}={value}")

# Callable type
def apply_func(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# Higher-order functions
def make_multiplier(n: int) -> Callable[[int], int]:
    def multiplier(x: int) -> int:
        return x * n
    return multiplier
```

### Overloads

```python
from typing import overload, Literal

@overload
def process(data: str) -> str: ...
@overload
def process(data: bytes) -> bytes: ...
@overload
def process(data: int) -> int: ...

def process(data: str | bytes | int) -> str | bytes | int:
    if isinstance(data, str):
        return data.upper()
    elif isinstance(data, bytes):
        return data.upper()
    else:
        return data * 2

# Overload with Literal
@overload
def fetch(url: str, format: Literal["json"]) -> dict: ...
@overload
def fetch(url: str, format: Literal["text"]) -> str: ...
@overload
def fetch(url: str, format: Literal["bytes"]) -> bytes: ...

def fetch(url: str, format: str) -> dict | str | bytes:
    # Implementation
    ...
```

### ParamSpec for Decorators

```python
from typing import ParamSpec, TypeVar, Callable
from functools import wraps

P = ParamSpec("P")
R = TypeVar("R")

def log_calls(func: Callable[P, R]) -> Callable[P, R]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a: int, b: int) -> int:
    return a + b

# Python 3.12+ syntax
def log_calls_new[**P, R](func: Callable[P, R]) -> Callable[P, R]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper
```

## Classes and Protocols

### Class Typing

```python
from typing import ClassVar, Self

class User:
    # Class variable
    count: ClassVar[int] = 0

    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
        User.count += 1

    # Self type for method chaining
    def with_name(self, name: str) -> Self:
        self.name = name
        return self

    def with_age(self, age: int) -> Self:
        self.age = age
        return self

# Usage
user = User("Alice", 30).with_name("Bob").with_age(25)
```

### Protocols (Structural Subtyping)

```python
from typing import Protocol, runtime_checkable

# Define a protocol (interface)
class Drawable(Protocol):
    def draw(self) -> None: ...

class Resizable(Protocol):
    def resize(self, width: int, height: int) -> None: ...

# Combining protocols
class DrawableAndResizable(Drawable, Resizable, Protocol):
    pass

# Implementation (no explicit inheritance needed!)
class Circle:
    def draw(self) -> None:
        print("Drawing circle")

class Rectangle:
    def draw(self) -> None:
        print("Drawing rectangle")

    def resize(self, width: int, height: int) -> None:
        print(f"Resizing to {width}x{height}")

# Works because Circle has draw() method
def render(shape: Drawable) -> None:
    shape.draw()

render(Circle())  # OK - Circle satisfies Drawable protocol

# Runtime checkable protocol
@runtime_checkable
class Closeable(Protocol):
    def close(self) -> None: ...

# Can use isinstance
if isinstance(file, Closeable):
    file.close()
```

### TypedDict

```python
from typing import TypedDict, Required, NotRequired

# Basic TypedDict
class Movie(TypedDict):
    title: str
    year: int
    director: str

movie: Movie = {"title": "Inception", "year": 2010, "director": "Nolan"}

# With optional keys
class UserProfile(TypedDict, total=False):
    name: str  # Optional
    email: str  # Optional
  

Related in General