python-type-safety

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Python Type Safety

Python类型安全

Leverage Python's type system to catch errors at static analysis time. Type annotations serve as enforced documentation that tooling validates automatically.
利用Python的类型系统在静态分析阶段捕获错误。类型注解可作为自动化工具验证的强制文档。

When to Use This Skill

何时使用此技能

  • Adding type hints to existing code
  • Creating generic, reusable classes
  • Defining structural interfaces with protocols
  • Configuring mypy or pyright for strict checking
  • Understanding type narrowing and guards
  • Building type-safe APIs and libraries
  • 为现有代码添加类型提示
  • 创建通用的可复用类
  • 使用协议定义结构化接口
  • 配置mypy或pyright进行严格检查
  • 理解类型收窄和类型守卫
  • 构建类型安全的API和库

Core Concepts

核心概念

1. Type Annotations

1. 类型注解

Declare expected types for function parameters, return values, and variables.
为函数参数、返回值和变量声明预期类型。

2. Generics

2. 泛型

Write reusable code that preserves type information across different types.
编写可复用的代码,在不同类型间保留类型信息。

3. Protocols

3. 协议

Define structural interfaces without inheritance (duck typing with type safety).
无需继承即可定义结构化接口(带类型安全的鸭子类型)。

4. Type Narrowing

4. 类型收窄

Use guards and conditionals to narrow types within code blocks.
使用守卫和条件语句在代码块内收窄类型范围。

Quick Start

快速开始

python
def get_user(user_id: str) -> User | None:
    """Return type makes 'might not exist' explicit."""
    ...
python
def get_user(user_id: str) -> User | None:
    """Return type makes 'might not exist' explicit."""
    ...

Type checker enforces handling None case

Type checker enforces handling None case

user = get_user("123") if user is None: raise UserNotFoundError("123") print(user.name) # Type checker knows user is User here
undefined
user = get_user("123") if user is None: raise UserNotFoundError("123") print(user.name) # Type checker knows user is User here
undefined

Fundamental Patterns

基础模式

Pattern 1: Annotate All Public Signatures

模式1:为所有公共签名添加注解

Every public function, method, and class should have type annotations.
python
def get_user(user_id: str) -> User:
    """Retrieve user by ID."""
    ...

def process_batch(
    items: list[Item],
    max_workers: int = 4,
) -> BatchResult[ProcessedItem]:
    """Process items concurrently."""
    ...

class UserRepository:
    def __init__(self, db: Database) -> None:
        self._db = db

    async def find_by_id(self, user_id: str) -> User | None:
        """Return User if found, None otherwise."""
        ...

    async def find_by_email(self, email: str) -> User | None:
        ...

    async def save(self, user: User) -> User:
        """Save and return user with generated ID."""
        ...
Use
mypy --strict
or
pyright
in CI to catch type errors early. For existing projects, enable strict mode incrementally using per-module overrides.
每个公共函数、方法和类都应包含类型注解。
python
def get_user(user_id: str) -> User:
    """Retrieve user by ID."""
    ...

def process_batch(
    items: list[Item],
    max_workers: int = 4,
) -> BatchResult[ProcessedItem]:
    """Process items concurrently."""
    ...

class UserRepository:
    def __init__(self, db: Database) -> None:
        self._db = db

    async def find_by_id(self, user_id: str) -> User | None:
        """Return User if found, None otherwise."""
        ...

    async def find_by_email(self, email: str) -> User | None:
        ...

    async def save(self, user: User) -> User:
        """Save and return user with generated ID."""
        ...
在CI中使用
mypy --strict
pyright
提前捕获类型错误。对于现有项目,可使用按模块覆盖的方式逐步启用严格模式。

Pattern 2: Use Modern Union Syntax

模式2:使用现代联合类型语法

Python 3.10+ provides cleaner union syntax.
python
undefined
Python 3.10+提供了更简洁的联合类型语法。
python
undefined

Preferred (3.10+)

推荐写法(3.10+)

def find_user(user_id: str) -> User | None: ...
def parse_value(v: str) -> int | float | str: ...
def find_user(user_id: str) -> User | None: ...
def parse_value(v: str) -> int | float | str: ...

Older style (still valid, needed for 3.9)

旧写法(仍然有效,适用于3.9版本)

from typing import Optional, Union
def find_user(user_id: str) -> Optional[User]: ...
undefined
from typing import Optional, Union
def find_user(user_id: str) -> Optional[User]: ...
undefined

Pattern 3: Type Narrowing with Guards

模式3:使用守卫进行类型收窄

Use conditionals to narrow types for the type checker.
python
def process_user(user_id: str) -> UserData:
    user = find_user(user_id)

    if user is None:
        raise UserNotFoundError(f"User {user_id} not found")

    # Type checker knows user is User here, not User | None
    return UserData(
        name=user.name,
        email=user.email,
    )

def process_items(items: list[Item | None]) -> list[ProcessedItem]:
    # Filter and narrow types
    valid_items = [item for item in items if item is not None]
    # valid_items is now list[Item]
    return [process(item) for item in valid_items]
使用条件语句帮助类型检查器收窄类型范围。
python
def process_user(user_id: str) -> UserData:
    user = find_user(user_id)

    if user is None:
        raise UserNotFoundError(f"User {user_id} not found")

    # 类型检查器此时知道user是User类型,而非User | None
    return UserData(
        name=user.name,
        email=user.email,
    )

def process_items(items: list[Item | None]) -> list[ProcessedItem]:
    # 过滤并收窄类型
    valid_items = [item for item in items if item is not None]
    # valid_items现在是list[Item]类型
    return [process(item) for item in valid_items]

Pattern 4: Generic Classes

模式4:泛型类

Create type-safe reusable containers.
python
from typing import TypeVar, Generic

T = TypeVar("T")
E = TypeVar("E", bound=Exception)

class Result(Generic[T, E]):
    """Represents either a success value or an error."""

    def __init__(
        self,
        value: T | None = None,
        error: E | None = None,
    ) -> None:
        if (value is None) == (error is None):
            raise ValueError("Exactly one of value or error must be set")
        self._value = value
        self._error = error

    @property
    def is_success(self) -> bool:
        return self._error is None

    @property
    def is_failure(self) -> bool:
        return self._error is not None

    def unwrap(self) -> T:
        """Get value or raise the error."""
        if self._error is not None:
            raise self._error
        return self._value  # type: ignore[return-value]

    def unwrap_or(self, default: T) -> T:
        """Get value or return default."""
        if self._error is not None:
            return default
        return self._value  # type: ignore[return-value]
创建类型安全的可复用容器。
python
from typing import TypeVar, Generic

T = TypeVar("T")
E = TypeVar("E", bound=Exception)

class Result(Generic[T, E]):
    """Represents either a success value or an error."""

    def __init__(
        self,
        value: T | None = None,
        error: E | None = None,
    ) -> None:
        if (value is None) == (error is None):
            raise ValueError("Exactly one of value or error must be set")
        self._value = value
        self._error = error

    @property
    def is_success(self) -> bool:
        return self._error is None

    @property
    def is_failure(self) -> bool:
        return self._error is not None

    def unwrap(self) -> T:
        """Get value or raise the error."""
        if self._error is not None:
            raise self._error
        return self._value  # type: ignore[return-value]

    def unwrap_or(self, default: T) -> T:
        """Get value or return default."""
        if self._error is not None:
            return default
        return self._value  # type: ignore[return-value]

Usage preserves types

使用时保留类型信息

def parse_config(path: str) -> Result[Config, ConfigError]: try: return Result(value=Config.from_file(path)) except ConfigError as e: return Result(error=e)
result = parse_config("config.yaml") if result.is_success: config = result.unwrap() # Type: Config
undefined
def parse_config(path: str) -> Result[Config, ConfigError]: try: return Result(value=Config.from_file(path)) except ConfigError as e: return Result(error=e)
result = parse_config("config.yaml") if result.is_success: config = result.unwrap() # Type: Config
undefined

Advanced Patterns

进阶模式

Pattern 5: Generic Repository

模式5:泛型仓库

Create type-safe data access patterns.
python
from typing import TypeVar, Generic
from abc import ABC, abstractmethod

T = TypeVar("T")
ID = TypeVar("ID")

class Repository(ABC, Generic[T, ID]):
    """Generic repository interface."""

    @abstractmethod
    async def get(self, id: ID) -> T | None:
        """Get entity by ID."""
        ...

    @abstractmethod
    async def save(self, entity: T) -> T:
        """Save and return entity."""
        ...

    @abstractmethod
    async def delete(self, id: ID) -> bool:
        """Delete entity, return True if existed."""
        ...

class UserRepository(Repository[User, str]):
    """Concrete repository for Users with string IDs."""

    async def get(self, id: str) -> User | None:
        row = await self._db.fetchrow(
            "SELECT * FROM users WHERE id = $1", id
        )
        return User(**row) if row else None

    async def save(self, entity: User) -> User:
        ...

    async def delete(self, id: str) -> bool:
        ...
创建类型安全的数据访问模式。
python
from typing import TypeVar, Generic
from abc import ABC, abstractmethod

T = TypeVar("T")
ID = TypeVar("ID")

class Repository(ABC, Generic[T, ID]):
    """Generic repository interface."""

    @abstractmethod
    async def get(self, id: ID) -> T | None:
        """Get entity by ID."""
        ...

    @abstractmethod
    async def save(self, entity: T) -> T:
        """Save and return entity."""
        ...

    @abstractmethod
    async def delete(self, id: ID) -> bool:
        """Delete entity, return True if existed."""
        ...

class UserRepository(Repository[User, str]):
    """Concrete repository for Users with string IDs."""

    async def get(self, id: str) -> User | None:
        row = await self._db.fetchrow(
            "SELECT * FROM users WHERE id = $1", id
        )
        return User(**row) if row else None

    async def save(self, entity: User) -> User:
        ...

    async def delete(self, id: str) -> bool:
        ...

Pattern 6: TypeVar with Bounds

模式6:带边界的TypeVar

Restrict generic parameters to specific types.
python
from typing import TypeVar
from pydantic import BaseModel

ModelT = TypeVar("ModelT", bound=BaseModel)

def validate_and_create(model_cls: type[ModelT], data: dict) -> ModelT:
    """Create a validated Pydantic model from dict."""
    return model_cls.model_validate(data)
限制泛型参数为特定类型。
python
from typing import TypeVar
from pydantic import BaseModel

ModelT = TypeVar("ModelT", bound=BaseModel)

def validate_and_create(model_cls: type[ModelT], data: dict) -> ModelT:
    """Create a validated Pydantic model from dict."""
    return model_cls.model_validate(data)

Works with any BaseModel subclass

适用于任何BaseModel子类

class User(BaseModel): name: str email: str
user = validate_and_create(User, {"name": "Alice", "email": "a@b.com"})
class User(BaseModel): name: str email: str
user = validate_and_create(User, {"name": "Alice", "email": "a@b.com"})

user is typed as User

user的类型是User

Type error: str is not a BaseModel subclass

类型错误:str不是BaseModel子类

result = validate_and_create(str, {"name": "Alice"}) # Error!
undefined
result = validate_and_create(str, {"name": "Alice"}) # Error!
undefined

Pattern 7: Protocols for Structural Typing

模式7:用于结构化类型的协议

Define interfaces without requiring inheritance.
python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    """Any class that can be serialized to/from dict."""

    def to_dict(self) -> dict:
        ...

    @classmethod
    def from_dict(cls, data: dict) -> "Serializable":
        ...
无需继承即可定义接口。
python
from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    """Any class that can be serialized to/from dict."""

    def to_dict(self) -> dict:
        ...

    @classmethod
    def from_dict(cls, data: dict) -> "Serializable":
        ...

User satisfies Serializable without inheriting from it

User类无需继承即可满足Serializable协议

class User: def init(self, id: str, name: str) -> None: self.id = id self.name = name
def to_dict(self) -> dict:
    return {"id": self.id, "name": self.name}

@classmethod
def from_dict(cls, data: dict) -> "User":
    return cls(id=data["id"], name=data["name"])
def serialize(obj: Serializable) -> str: """Works with any Serializable object.""" return json.dumps(obj.to_dict())
class User: def init(self, id: str, name: str) -> None: self.id = id self.name = name
def to_dict(self) -> dict:
    return {"id": self.id, "name": self.name}

@classmethod
def from_dict(cls, data: dict) -> "User":
    return cls(id=data["id"], name=data["name"])
def serialize(obj: Serializable) -> str: """Works with any Serializable object.""" return json.dumps(obj.to_dict())

Works - User matches the protocol

可正常运行 - User符合协议

serialize(User("1", "Alice"))
serialize(User("1", "Alice"))

Runtime checking with @runtime_checkable

使用@runtime_checkable进行运行时检查

isinstance(User("1", "Alice"), Serializable) # True
undefined
isinstance(User("1", "Alice"), Serializable) # True
undefined

Pattern 8: Common Protocol Patterns

模式8:常见协议模式

Define reusable structural interfaces.
python
from typing import Protocol

class Closeable(Protocol):
    """Resource that can be closed."""
    def close(self) -> None: ...

class AsyncCloseable(Protocol):
    """Async resource that can be closed."""
    async def close(self) -> None: ...

class Readable(Protocol):
    """Object that can be read from."""
    def read(self, n: int = -1) -> bytes: ...

class HasId(Protocol):
    """Object with an ID property."""
    @property
    def id(self) -> str: ...

class Comparable(Protocol):
    """Object that supports comparison."""
    def __lt__(self, other: "Comparable") -> bool: ...
    def __le__(self, other: "Comparable") -> bool: ...
定义可复用的结构化接口。
python
from typing import Protocol

class Closeable(Protocol):
    """Resource that can be closed."""
    def close(self) -> None: ...

class AsyncCloseable(Protocol):
    """Async resource that can be closed."""
    async def close(self) -> None: ...

class Readable(Protocol):
    """Object that can be read from."""
    def read(self, n: int = -1) -> bytes: ...

class HasId(Protocol):
    """Object with an ID property."""
    @property
    def id(self) -> str: ...

class Comparable(Protocol):
    """Object that supports comparison."""
    def __lt__(self, other: "Comparable") -> bool: ...
    def __le__(self, other: "Comparable") -> bool: ...

Pattern 9: Type Aliases

模式9:类型别名

Create meaningful type names.
Note: The
type
statement was introduced in Python 3.10 for simple aliases. Generic type statements require Python 3.12+.
python
undefined
创建有意义的类型名称。
注意: Python 3.10引入了
type
语句用于简单别名。泛型类型别名需要Python 3.12+版本。
python
undefined

Python 3.10+ type statement for simple aliases

Python 3.10+ 使用type语句定义简单别名

type UserId = str type UserDict = dict[str, Any]
type UserId = str type UserDict = dict[str, Any]

Python 3.12+ type statement with generics

Python 3.12+ 使用type语句定义泛型别名

type Handler[T] = Callable[[Request], T] type AsyncHandler[T] = Callable[[Request], Awaitable[T]]
type Handler[T] = Callable[[Request], T] type AsyncHandler[T] = Callable[[Request], Awaitable[T]]

Python 3.9-3.11 style (needed for broader compatibility)

Python 3.9-3.11 写法(用于更广的兼容性)

from typing import TypeAlias from collections.abc import Callable, Awaitable
UserId: TypeAlias = str Handler: TypeAlias = Callable[[Request], Response]
from typing import TypeAlias from collections.abc import Callable, Awaitable
UserId: TypeAlias = str Handler: TypeAlias = Callable[[Request], Response]

Usage

使用示例

def register_handler(path: str, handler: Handler[Response]) -> None: ...
undefined
def register_handler(path: str, handler: Handler[Response]) -> None: ...
undefined

Pattern 10: Callable Types

模式10:可调用类型

Type function parameters and callbacks.
python
from collections.abc import Callable, Awaitable
为函数参数和回调函数添加类型注解。
python
from collections.abc import Callable, Awaitable

Sync callback

同步回调

ProgressCallback = Callable[[int, int], None] # (current, total)
ProgressCallback = Callable[[int, int], None] # (current, total)

Async callback

异步回调

AsyncHandler = Callable[[Request], Awaitable[Response]]
AsyncHandler = Callable[[Request], Awaitable[Response]]

With named parameters (using Protocol)

带命名参数的回调(使用Protocol)

class OnProgress(Protocol): def call( self, current: int, total: int, *, message: str = "", ) -> None: ...
def process_items( items: list[Item], on_progress: ProgressCallback | None = None, ) -> list[Result]: for i, item in enumerate(items): if on_progress: on_progress(i, len(items)) ...
undefined
class OnProgress(Protocol): def call( self, current: int, total: int, *, message: str = "", ) -> None: ...
def process_items( items: list[Item], on_progress: ProgressCallback | None = None, ) -> list[Result]: for i, item in enumerate(items): if on_progress: on_progress(i, len(items)) ...
undefined

Configuration

配置

Strict Mode Checklist

严格模式检查清单

For
mypy --strict
compliance:
toml
undefined
要符合
mypy --strict
要求:
toml
undefined

pyproject.toml

pyproject.toml

[tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_ignores = true disallow_untyped_defs = true disallow_incomplete_defs = true no_implicit_optional = true

Incremental adoption goals:
- All function parameters annotated
- All return types annotated
- Class attributes annotated
- Minimize `Any` usage (acceptable for truly dynamic data)
- Generic collections use type parameters (`list[str]` not `list`)

For existing codebases, enable strict mode per-module using `# mypy: strict` or configure per-module overrides in `pyproject.toml`.
[tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_ignores = true disallow_untyped_defs = true disallow_incomplete_defs = true no_implicit_optional = true

逐步采用目标:
- 所有函数参数都添加注解
- 所有返回类型都添加注解
- 类属性都添加注解
- 尽量减少`Any`的使用(仅适用于真正动态的数据)
- 泛型集合使用类型参数(`list[str]`而非`list`)

对于现有代码库,可使用`# mypy: strict`按模块启用严格模式,或在`pyproject.toml`中配置按模块覆盖。

Best Practices Summary

最佳实践总结

  1. Annotate all public APIs - Functions, methods, class attributes
  2. Use
    T | None
    - Modern union syntax over
    Optional[T]
  3. Run strict type checking -
    mypy --strict
    in CI
  4. Use generics - Preserve type info in reusable code
  5. Define protocols - Structural typing for interfaces
  6. Narrow types - Use guards to help the type checker
  7. Bound type vars - Restrict generics to meaningful types
  8. Create type aliases - Meaningful names for complex types
  9. Minimize
    Any
    - Use specific types or generics.
    Any
    is acceptable for truly dynamic data or when interfacing with untyped third-party code
  10. Document with types - Types are enforceable documentation
  1. 为所有公共API添加注解 - 函数、方法、类属性
  2. 使用
    T | None
    - 现代联合类型语法替代
    Optional[T]
  3. 运行严格类型检查 - 在CI中使用
    mypy --strict
  4. 使用泛型 - 在可复用代码中保留类型信息
  5. 定义协议 - 用于结构化类型的接口
  6. 收窄类型 - 使用守卫帮助类型检查器
  7. 绑定类型变量 - 限制泛型为有意义的类型
  8. 创建类型别名 - 为复杂类型赋予有意义的名称
  9. 尽量减少
    Any
    - 使用特定类型或泛型。仅在处理真正动态的数据或与无类型第三方代码交互时使用
    Any
  10. 用类型进行文档说明 - 类型是可强制执行的文档