python-type-system

Master Python's type system to write type-safe, maintainable code. This skill covers type hints, static type checking with mypy, and advanced typing features.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "python-type-system" with this command: npx skills add thebushidocollective/han/thebushidocollective-han-python-type-system

Python Type System

Master Python's type system to write type-safe, maintainable code. This skill covers type hints, static type checking with mypy, and advanced typing features.

Type Checking Tools

Install mypy for static type checking

pip install mypy

Run mypy on a file or directory

mypy my_module.py mypy src/

Run with specific configuration

mypy --config-file mypy.ini src/

Run with strict mode

mypy --strict src/

Show type coverage report

mypy --html-report mypy-report src/

mypy Configuration

mypy.ini configuration file:

[mypy]

Global options

python_version = 3.11 warn_return_any = True warn_unused_configs = True disallow_untyped_defs = True disallow_any_unimported = True no_implicit_optional = True warn_redundant_casts = True warn_unused_ignores = True warn_no_return = True check_untyped_defs = True strict_equality = True

Per-module options

[mypy-tests.*] disallow_untyped_defs = False

[mypy-third_party.*] ignore_missing_imports = True

pyproject.toml configuration:

[tool.mypy] python_version = "3.11" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true disallow_any_unimported = true no_implicit_optional = true warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true check_untyped_defs = true strict_equality = true

[[tool.mypy.overrides]] module = "tests.*" disallow_untyped_defs = false

Basic Type Hints

Primitive types and collections:

from typing import List, Dict, Set, Tuple, Optional, Union, Any

Basic types

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

Collections

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

Optional (can be None)

def find_user(user_id: int) -> Optional[str]: users = {1: "Alice", 2: "Bob"} return users.get(user_id)

Union types (multiple possible types)

def process_value(value: Union[int, str]) -> str: return str(value)

Tuple with fixed types

def get_coordinates() -> Tuple[float, float]: return (37.7749, -122.4194)

Any type (avoid when possible)

def process_data(data: Any) -> None: print(data)

Modern Type Syntax (Python 3.10+)

Using PEP 604 union syntax:

Python 3.10+ union syntax with |

def process_value(value: int | str) -> str: return str(value)

Optional with | None

def find_user(user_id: int) -> str | None: users = {1: "Alice", 2: "Bob"} return users.get(user_id)

Multiple unions

def handle_response( response: dict | list | str | None ) -> str: if response is None: return "No response" return str(response)

Built-in generic types (Python 3.9+):

Use built-in types instead of typing module

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

def get_mapping() -> dict[str, list[int]]: return {"numbers": [1, 2, 3]}

def get_unique(items: list[str]) -> set[str]: return set(items)

Nested generics

def group_items( items: list[tuple[str, int]] ) -> dict[str, list[int]]: result: dict[str, list[int]] = {} for key, value in items: result.setdefault(key, []).append(value) return result

Generic Types

Creating generic functions and classes:

from typing import TypeVar, Generic, Sequence

Type variable

T = TypeVar("T")

def first(items: Sequence[T]) -> T | None: return items[0] if items else None

def last(items: list[T]) -> T | None: return items[-1] if items else None

Constrained type variable

Number = TypeVar("Number", int, float)

def add(a: Number, b: Number) -> Number: return a + b # type: ignore

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 peek(self) -> T | None:
    return self._items[-1] if self._items else None

Usage

stack: Stack[int] = Stack() stack.push(1) stack.push(2) value: int = stack.pop()

Bound type variables:

from typing import TypeVar from collections.abc import Sized

Type variable with upper bound

TSized = TypeVar("TSized", bound=Sized)

def get_length(obj: TSized) -> int: return len(obj)

Works with any Sized type

get_length("hello") get_length([1, 2, 3]) get_length({"a": 1})

Protocol (Structural Subtyping)

Define interfaces using Protocol:

from typing import Protocol

Define a protocol

class Drawable(Protocol): def draw(self) -> str: ...

Classes that match the protocol don't need inheritance

class Circle: def draw(self) -> str: return "Drawing circle"

class Square: def draw(self) -> str: return "Drawing square"

Function accepts any type matching the protocol

def render(shape: Drawable) -> None: print(shape.draw())

Works with any matching class

render(Circle()) render(Square())

Protocol with properties and methods:

from typing import Protocol

class Comparable(Protocol): def lt(self, other: "Comparable") -> bool: ...

def __gt__(self, other: "Comparable") -> bool:
    ...

def find_max(items: list[Comparable]) -> Comparable: return max(items)

class Person: def init(self, name: str, age: int) -> None: self.name = name self.age = age

def __lt__(self, other: "Person") -> bool:
    return self.age < other.age

def __gt__(self, other: "Person") -> bool:
    return self.age > other.age

Works because Person implements Comparable protocol

people = [Person("Alice", 30), Person("Bob", 25)] oldest = find_max(people)

Runtime checkable protocols:

from typing import Protocol, runtime_checkable

@runtime_checkable class Serializable(Protocol): def to_dict(self) -> dict[str, Any]: ...

class User: def init(self, name: str) -> None: self.name = name

def to_dict(self) -> dict[str, Any]:
    return {"name": self.name}

user = User("Alice") assert isinstance(user, Serializable)

TypedDict

Define dictionary shapes with TypedDict:

from typing import TypedDict, NotRequired

Basic TypedDict

class UserDict(TypedDict): id: int name: str email: str

def create_user(user: UserDict) -> UserDict: return user

user: UserDict = { "id": 1, "name": "Alice", "email": "alice@example.com" }

Optional fields (Python 3.11+)

class PersonDict(TypedDict): name: str age: int email: NotRequired[str] # Optional field

person: PersonDict = {"name": "Bob", "age": 30}

Total=False makes all fields optional

class ConfigDict(TypedDict, total=False): host: str port: int debug: bool

config: ConfigDict = {"host": "localhost"}

Inheritance with TypedDict:

from typing import TypedDict

class BaseUserDict(TypedDict): id: int name: str

class ExtendedUserDict(BaseUserDict): email: str is_active: bool

user: ExtendedUserDict = { "id": 1, "name": "Alice", "email": "alice@example.com", "is_active": True }

Literal Types

Restrict values to specific literals:

from typing import Literal

def set_mode(mode: Literal["read", "write", "append"]) -> None: print(f"Mode set to {mode}")

Valid

set_mode("read")

Type error: not a valid literal

set_mode("invalid")

Literal unions

Status = Literal["pending", "active", "completed"]

def update_status(status: Status) -> None: print(f"Status: {status}")

Literal with multiple types

MixedLiteral = Literal[True, 1, "one"]

Type Aliases

Create type aliases for complex types:

from typing import TypeAlias

Type alias

UserId: TypeAlias = int UserName: TypeAlias = str

def get_user(user_id: UserId) -> UserName: return f"User {user_id}"

Complex type alias

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

def process_json(data: JsonValue) -> None: print(data)

Generic type alias

Vector: TypeAlias = list[float] Matrix: TypeAlias = list[Vector]

def multiply_matrix(a: Matrix, b: Matrix) -> Matrix: # Implementation return [[0.0]]

Callable Types

Type hints for functions and callables:

from typing import Callable

Function that takes a callback

def apply_operation( x: int, y: int, operation: Callable[[int, int], int] ) -> int: return operation(x, y)

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

result = apply_operation(5, 3, add)

Callable with no arguments

def execute(task: Callable[[], None]) -> None: task()

Callback with multiple argument types

Callback: TypeAlias = Callable[[str, int], bool]

def register_handler(callback: Callback) -> None: callback("test", 42)

ParamSpec and Concatenate

Advanced callable typing:

from typing import Callable, ParamSpec, TypeVar, Concatenate from functools import wraps

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

Decorator that preserves function signature

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

Concatenate adds parameters

def with_context( func: Callable[Concatenate[str, P], R] ) -> Callable[P, R]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return func("context", *args, **kwargs) return wrapper

Type Guards

Create type guards for runtime type narrowing:

from typing import TypeGuard

def is_str_list(val: list[object]) -> TypeGuard[list[str]]: return all(isinstance(x, str) for x in val)

def process_strings(values: list[object]) -> None: if is_str_list(values): # Type narrowed to list[str] for value in values: print(value.upper())

More complex type guard

def is_non_empty_str(val: str | None) -> TypeGuard[str]: return val is not None and len(val) > 0

def process_name(name: str | None) -> None: if is_non_empty_str(name): # Type narrowed to str (non-None) print(name.upper())

Overload

Multiple function signatures with overload:

from typing import overload, Literal

@overload def get_value(key: str, as_int: Literal[True]) -> int: ...

@overload def get_value(key: str, as_int: Literal[False]) -> str: ...

def get_value(key: str, as_int: bool) -> int | str: value = "42" return int(value) if as_int else value

Type checker knows return type based on literal

int_value: int = get_value("key", True) str_value: str = get_value("key", False)

Common Patterns

Avoiding common type checking issues:

from typing import TYPE_CHECKING, cast

Avoid circular imports

if TYPE_CHECKING: from my_module import MyClass

def process(obj: "MyClass") -> None: pass

Type casting when you know better than the type checker

def get_data() -> object: return {"key": "value"}

data = cast(dict[str, str], get_data())

Assert type with reveal_type (mypy only)

x = [1, 2, 3]

reveal_type(x) # Reveals: list[int]

Ignore type checking for specific line

result = some_untyped_function() # type: ignore[no-untyped-call]

Ignore specific error code

value: Any = get_dynamic_value() processed = process_value(value) # type: ignore[arg-type]

When to Use This Skill

Use python-type-system when you need to:

  • Add type hints to Python code for better IDE support and documentation

  • Configure mypy for static type checking in your project

  • Create reusable generic functions and classes

  • Define structural interfaces using Protocol

  • Specify exact dictionary shapes with TypedDict

  • Create type-safe decorators with ParamSpec

  • Implement runtime type narrowing with TypeGuard

  • Handle complex union types and literal types

  • Build type-safe APIs and library interfaces

Best Practices

  • Enable strict mode in mypy for maximum type safety

  • Use Protocol for structural typing instead of ABC when possible

  • Prefer built-in generic types (list, dict) over typing module (3.9+)

  • Use TypedDict for dictionary shapes instead of Dict[str, Any]

  • Create type aliases for complex types to improve readability

  • Use TYPE_CHECKING to avoid circular import issues

  • Add type hints incrementally, starting with public APIs

  • Run mypy in CI/CD to catch type errors early

  • Use reveal_type during development to debug type inference

  • Avoid Any type except when interfacing with untyped code

Common Pitfalls

  • Forgetting to handle None in Optional types

  • Using mutable default arguments (use None and create in function)

  • Not using Protocol for duck-typed interfaces

  • Overusing Any type, reducing type safety benefits

  • Not enabling strict mode in mypy configuration

  • Ignoring type errors instead of fixing them properly

  • Using old typing syntax (List, Dict) in Python 3.9+

  • Circular import issues with forward references

  • Not understanding variance in generic types

  • Mixing runtime behavior with type hints (use TypeGuard)

Resources

  • Python Type Hints Documentation

  • mypy Documentation

  • PEP 484 - Type Hints

  • PEP 544 - Protocols

  • PEP 589 - TypedDict

  • PEP 604 - Union Syntax

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-type-system

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

typescript-async-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

c-systems-programming

No summary provided by upstream source.

Repository SourceNeeds Review