
python-type-system
by knopki
There's no place like $HOME
SKILL.md
name: python-type-system description: Use when Python's type system including type hints, mypy, Protocol, TypedDict, and Generics. Use when working with Python type annotations.
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
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


