python-type-safety by wshobson/agents
npx skills add https://github.com/wshobson/agents --skill python-type-safety利用 Python 的类型系统在静态分析时捕获错误。类型注解可作为强制执行的文档,由工具自动验证。
为函数参数、返回值和变量声明期望的类型。
编写可复用的代码,在不同类型间保持类型信息。
无需继承即可定义结构化接口(具备类型安全的鸭子类型)。
使用守卫和条件语句在代码块内收窄类型。
def get_user(user_id: str) -> User | None:
"""返回类型明确表示‘可能不存在’。"""
...
# 类型检查器强制处理 None 情况
user = get_user("123")
if user is None:
raise UserNotFoundError("123")
print(user.name) # 类型检查器知道此处 user 是 User 类型
每个公共函数、方法和类都应该有类型注解。
def get_user(user_id: str) -> User:
"""根据 ID 检索用户。"""
...
def process_batch(
items: list[Item],
max_workers: int = 4,
) -> BatchResult[ProcessedItem]:
"""并发处理项目。"""
...
class UserRepository:
def __init__(self, db: Database) -> None:
self._db = db
async def find_by_id(self, user_id: str) -> User | None:
"""如果找到则返回 User,否则返回 None。"""
...
async def find_by_email(self, email: str) -> User | None:
...
async def save(self, user: User) -> User:
"""保存并返回带有生成 ID 的用户。"""
...
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在 CI 中使用 mypy --strict 或 pyright 及早捕获类型错误。对于现有项目,使用按模块覆盖的方式逐步启用严格模式。
Python 3.10+ 提供了更简洁的联合类型语法。
# 推荐 (3.10+)
def find_user(user_id: str) -> User | None:
...
def parse_value(v: str) -> int | float | str:
...
# 旧风格 (仍然有效,3.9 需要)
from typing import Optional, Union
def find_user(user_id: str) -> Optional[User]:
...
使用条件语句为类型检查器收窄类型。
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]
创建类型安全的可复用容器。
from typing import TypeVar, Generic
T = TypeVar("T")
E = TypeVar("E", bound=Exception)
class Result(Generic[T, E]):
"""表示一个成功值或一个错误。"""
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:
"""获取值或引发错误。"""
if self._error is not None:
raise self._error
return self._value # type: ignore[return-value]
def unwrap_or(self, default: T) -> T:
"""获取值或返回默认值。"""
if self._error is not None:
return default
return self._value # type: ignore[return-value]
# 使用方式保持类型
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() # 类型: Config
创建类型安全的数据访问模式。
from typing import TypeVar, Generic
from abc import ABC, abstractmethod
T = TypeVar("T")
ID = TypeVar("ID")
class Repository(ABC, Generic[T, ID]):
"""通用存储库接口。"""
@abstractmethod
async def get(self, id: ID) -> T | None:
"""根据 ID 获取实体。"""
...
@abstractmethod
async def save(self, entity: T) -> T:
"""保存并返回实体。"""
...
@abstractmethod
async def delete(self, id: ID) -> bool:
"""删除实体,如果存在则返回 True。"""
...
class UserRepository(Repository[User, str]):
"""使用字符串 ID 的 User 具体存储库。"""
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:
...
将泛型参数限制为特定类型。
from typing import TypeVar
from pydantic import BaseModel
ModelT = TypeVar("ModelT", bound=BaseModel)
def validate_and_create(model_cls: type[ModelT], data: dict) -> ModelT:
"""从字典创建经过验证的 Pydantic 模型。"""
return model_cls.model_validate(data)
# 适用于任何 BaseModel 子类
class User(BaseModel):
name: str
email: str
user = validate_and_create(User, {"name": "Alice", "email": "a@b.com"})
# user 被类型化为 User
# 类型错误:str 不是 BaseModel 子类
result = validate_and_create(str, {"name": "Alice"}) # 错误!
无需继承即可定义接口。
from typing import Protocol, runtime_checkable
@runtime_checkable
class Serializable(Protocol):
"""任何可以序列化到/从字典的类。"""
def to_dict(self) -> dict:
...
@classmethod
def from_dict(cls, data: dict) -> "Serializable":
...
# 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:
"""适用于任何 Serializable 对象。"""
return json.dumps(obj.to_dict())
# 有效 - User 匹配协议
serialize(User("1", "Alice"))
# 使用 @runtime_checkable 进行运行时检查
isinstance(User("1", "Alice"), Serializable) # True
定义可复用的结构化接口。
from typing import Protocol
class Closeable(Protocol):
"""可以关闭的资源。"""
def close(self) -> None: ...
class AsyncCloseable(Protocol):
"""可以关闭的异步资源。"""
async def close(self) -> None: ...
class Readable(Protocol):
"""可以从中读取的对象。"""
def read(self, n: int = -1) -> bytes: ...
class HasId(Protocol):
"""具有 ID 属性的对象。"""
@property
def id(self) -> str: ...
class Comparable(Protocol):
"""支持比较的对象。"""
def __lt__(self, other: "Comparable") -> bool: ...
def __le__(self, other: "Comparable") -> bool: ...
创建有意义的类型名称。
注意: type 语句在 Python 3.10 中引入,用于简单别名。泛型 type 语句需要 Python 3.12+。
# Python 3.10+ 的 type 语句用于简单别名
type UserId = str
type UserDict = dict[str, Any]
# Python 3.12+ 的 type 语句带泛型
type Handler[T] = Callable[[Request], T]
type AsyncHandler[T] = Callable[[Request], Awaitable[T]]
# Python 3.9-3.11 风格 (需要更广泛的兼容性)
from typing import TypeAlias
from collections.abc import Callable, Awaitable
UserId: TypeAlias = str
Handler: TypeAlias = Callable[[Request], Response]
# 用法
def register_handler(path: str, handler: Handler[Response]) -> None:
...
类型化函数参数和回调。
from collections.abc import Callable, Awaitable
# 同步回调
ProgressCallback = Callable[[int, int], None] # (current, total)
# 异步回调
AsyncHandler = Callable[[Request], Awaitable[Response]]
# 带命名参数 (使用 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))
...
为了符合 mypy --strict:
# 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 中的按模块覆盖来启用严格模式。
T | None - 现代联合类型语法优于 Optional[T]mypy --strictAny - 使用特定类型或泛型。Any 对于真正的动态数据或与无类型的第三方代码交互时可接受每周安装量
3.2K
代码仓库
GitHub 星标
32.2K
首次出现
2026年1月30日
安全审计
安装于
opencode2.6K
gemini-cli2.6K
codex2.5K
claude-code2.4K
cursor2.3K
github-copilot2.3K
Leverage Python's type system to catch errors at static analysis time. Type annotations serve as enforced documentation that tooling validates automatically.
Declare expected types for function parameters, return values, and variables.
Write reusable code that preserves type information across different types.
Define structural interfaces without inheritance (duck typing with type safety).
Use guards and conditionals to narrow types within code blocks.
def get_user(user_id: str) -> User | None:
"""Return type makes 'might not exist' explicit."""
...
# 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
Every public function, method, and class should have type annotations.
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 3.10+ provides cleaner union syntax.
# Preferred (3.10+)
def find_user(user_id: str) -> User | None:
...
def parse_value(v: str) -> int | float | str:
...
# Older style (still valid, needed for 3.9)
from typing import Optional, Union
def find_user(user_id: str) -> Optional[User]:
...
Use conditionals to narrow types for the type checker.
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]
Create type-safe reusable containers.
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
Create type-safe data access patterns.
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:
...
Restrict generic parameters to specific types.
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
class User(BaseModel):
name: str
email: str
user = validate_and_create(User, {"name": "Alice", "email": "a@b.com"})
# user is typed as User
# Type error: str is not a BaseModel subclass
result = validate_and_create(str, {"name": "Alice"}) # Error!
Define interfaces without requiring inheritance.
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
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
serialize(User("1", "Alice"))
# Runtime checking with @runtime_checkable
isinstance(User("1", "Alice"), Serializable) # True
Define reusable structural interfaces.
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: ...
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 3.10+ type statement for simple aliases
type UserId = str
type UserDict = dict[str, Any]
# Python 3.12+ type statement with generics
type Handler[T] = Callable[[Request], T]
type AsyncHandler[T] = Callable[[Request], Awaitable[T]]
# Python 3.9-3.11 style (needed for broader compatibility)
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:
...
Type function parameters and callbacks.
from collections.abc import Callable, Awaitable
# Sync callback
ProgressCallback = Callable[[int, int], None] # (current, total)
# Async callback
AsyncHandler = Callable[[Request], Awaitable[Response]]
# With named parameters (using 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))
...
For mypy --strict compliance:
# 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:
Any usage (acceptable for truly dynamic data)list[str] not list)For existing codebases, enable strict mode per-module using # mypy: strict or configure per-module overrides in pyproject.toml.
T | None - Modern union syntax over Optional[T]mypy --strict in CIAny - Use specific types or generics. Any is acceptable for truly dynamic data or when interfacing with untyped third-party codeWeekly Installs
3.2K
Repository
GitHub Stars
32.2K
First Seen
Jan 30, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode2.6K
gemini-cli2.6K
codex2.5K
claude-code2.4K
cursor2.3K
github-copilot2.3K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装