ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

docling dignified-python 技能指南:Python 3.12 类型注解——PEP 695、type 语句与 Self 的完整实践

2026/9/7 18:36:29 拓冰建站 浏览量
docling dignified-python 技能指南:Python 3.12 类型注解——PEP 695、type 语句与 Self 的完整实践 docling dignified-python 技能指南Python 3.12 类型注解——PEP 695、type 语句与 Self 的完整实践【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling本文以 docling 仓库中 Agent 技能文档 .agents/skills/dignified-python/versions/python-3.12.md 为核心系统讲解 Python 3.12 的类型注解体系PEP 695 类型参数语法、type类型别名语句、Self自返回类型与|联合类型等写法并结合 docling 源码中的泛型实现、类型检查器ty配置给出可直接落地的编码规范与迁移路径。读完本文你可以掌握一套完整的现代 Python 类型标注规范并理解 docling 这类多 Python 版本兼容项目为何选择TypeVarGeneric旧语法的原因。背景这份文档在 docling 中的定位python-3.12.md是 docling 仓库内置的 Dignified Python 技能的一个版本专属参考文件由 技能入口文件 按版本号自动选择加载技能首先通过pyproject.toml的requires-python字段或setup.py、.python-version识别项目最低 Python 版本若最低版本为 3.12 及以上则加载versions/python-3.12.md这份类型注解指南该技能定位是观点明确的生产级 Python 标准opinionated production Python standards覆盖 3.10–3.13属于通用 Python 风格指导项目自身约定可以覆盖它。需要注意 docling 自身的版本前提根 pyproject.toml 中声明requires-python 3.10,4.0ruff 的target-version py310类型检查器ty的python-version 3.10。也就是说docling 源码本身必须兼容 Python 3.10因此 3.12 才引入的 PEP 695 语法不能直接写入 docling 生产代码——这正是理解本文所有新旧写法对比的关键背景文档描述的是当项目最低版本为 3.12 时应如何写而 docling 源码展示的是最低版本为 3.10 时应如何写。Python 3.12 类型系统总览文档给出的 3.12 类型注解能力分三个时间层次3.12 新增Whats new in 3.12PEP 695 类型参数语法def funcT - Ttype语句用于更明确的类型别名更简洁的泛型类语法class Stack[T]3.11 起可用Self类型用于返回自身实例的方法3.10 起可用内建泛型类型list[T]、dict[K, V]等|操作符联合类型X | None表示 Optional还需要从 typing 模块引入的内容名称用途Self自返回方法TypeVar仅限受限/有界的泛型Protocol结构化类型较少用优先 ABCTYPE_CHECKING条件导入避免运行时循环依赖Any尽量少用基础集合类型用内建泛型弃用 typing 大写版本✅ 推荐写法——直接使用内建泛型names: list[str] [] mapping: dict[str, int] {} unique_ids: set[str] set() coordinates: tuple[int, int] (0, 0)❌ 错误写法——不要再用typing模块的大写等价物from typing import List, Dict, Set, Tuple # Dont do this names: List[str] []这一条与 docling 的 ruff 配置可以互相印证ruff 的 lint 规则中启用了UPpyupgrade类别即自动建议把typing.List/Dict迁移为内建泛型的升级规则见 pyproject.toml 的[tool.ruff.lint]段。联合类型与 Optional✅ 推荐|操作符def process(value: str | int) - str: return str(value) def find_config(name: str) - dict[str, str] | dict[str, int]: ... # 多路联合 def parse(input: str | int | float) - str: return str(input)❌ 错误不要使用typing.Unionfrom typing import Union def process(value: Union[str, int]) - str: # Dont do this ...✅ 推荐 OptionalX | Nonedef find_user(id: str) - User | None: Returns user or None if not found. if id in users: return users[id] return None❌ 错误不要使用typing.Optionalfrom typing import Optional def find_user(id: str) - Optional[User]: # Dont do this ...Self自返回方法的正确类型流式接口Builder 等方法返回self时用typing.Self标注类型检查器就能正确推断链式调用结果from typing import Self class Builder: def set_name(self, name: str) - Self: self.name name return self def set_value(self, value: int) - Self: self.value value return selfSelf自 Python 3.11 可用在最低版本为 3.12 的项目中应始终优先使用。PEP 695 泛型函数3.12 新增✅ 推荐PEP 695 类型参数语法def firstT - T | None: Return first item or None if empty. if not items: return None return items[0] def identityT - T: Return value unchanged. return value # 多个类型参数 def zip_dictsK, V - dict[K, V]: Create dict from separate key and value lists. return dict(zip(keys, values)) 仍然合法TypeVar写法from typing import TypeVar T TypeVar(T) def first(items: list[T]) - T | None: if not items: return None return items[0]原则简单泛型优先 PEP 695TypeVar仅在需要约束/边界时保留。PEP 695 泛型类3.12 新增✅ 推荐class Stack[T]语法class Stack[T]: A generic stack data structure. def __init__(self) - None: self._items: list[T] [] def push(self, item: T) - Self: self._items.append(item) return self def pop(self) - T | None: if not self._items: return None return self._items.pop() # 使用 int_stack Stack[int]() int_stack.push(42).push(43) 仍然合法Generic[T]旧写法from typing import Generic, TypeVar T TypeVar(T) class Stack(Generic[T]): def __init__(self) - None: self._items: list[T] [] # ... rest of implementationPEP 695 的优势无需导入、类型参数作用域局限在类内。这一点在 docling 源码中可以看到旧写法的真实规模从源码结构看docling 大量使用TypeVarGeneric模式例如 docling/models/factories/base_factory.py 的BaseFactory(Generic[A], metaclassABCMeta)、docling/models/base_model.py 的GenericEnrichmentModel(ABC, Generic[EnrichElementT])、docling/service_client/job.py 中的_JobHandlers(Generic[T_Result])与_ConversionJobBase(Generic[T_Result])以及 docling/datamodel/service/requests.py 的GenericChunkDocumentsRequest(..., Generic[ChunkingOptT])——因为项目要兼容 3.10这些类无法改用class X[T]语法。类型参数边界PEP 695 支持 bound不支持 constraints✅ PEP 695 中用冒号声明边界class Comparable: def compare(self, other: object) - int: ... def max_valueT: Comparable - T: Get maximum value from comparable items. return max(items, keylambda x: x)✅ 受限constrained限定在若干具体类型中仍必须用TypeVarfrom typing import TypeVar # 约束为具体类型——必须用 TypeVar Numeric TypeVar(Numeric, int, float) def add(a: Numeric, b: Numeric) - Numeric: return a b❌ 错误——PEP 695 语法无法表达 constraints# 这并不会把参数约束到 int | float def addNumeric - Numeric: return a btype 语句更明确的类型别名3.12 新增✅ 推荐type语句# 简单别名 type UserId str type Config dict[str, str | int | bool] # 泛型类型别名 type Result[T] tuple[T, str | None] def process(value: str) - Result[int]: try: return (int(value), None) except ValueError as e: return (0, str(e)) 仍然合法普通赋值别名UserId str # 依然有效 Config dict[str, str | int | bool] # 依然有效type语句更显式且支持带类型参数的泛型别名普通赋值别名做不到。Callable 类型使用 collections.abc✅ 推荐从collections.abc导入Callablefrom collections.abc import Callable # 接收 int、返回 str 的函数 processor: Callable[[int], str] str # 无参、返回 None callback: Callable[[], None] lambda: None # 多参数 validator: Callable[[str, int], bool] lambda s, i: len(s) i何时需要 fromfutureimport annotations三种典型场景1. 前向引用类引用自身from __future__ import annotations class Node: def __init__(self, value: int, parent: Node | None None): self.value value self.parent parent2. 循环类型导入配合TYPE_CHECKING# a.py from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from b import B class A: def method(self) - B: ...3. 复杂的递归类型from __future__ import annotations type JsonValue dict[str, JsonValue] | list[JsonValue] | str | int | float | bool | Nonedocling 源码对模式 2 使用了很高的密度if TYPE_CHECKING:块出现在 docling/datamodel/document.py、docling/backend/abstract_backend.py、docling/models/inference_engines/vlm/base.py 等 40 多个模块中是大型项目中消除运行时循环导入的标准手段。接口设计ABC 优先Protocol 为辅✅ 推荐 ABC让继承关系与意图显式化from abc import ABC, abstractmethod class Repository(ABC): abstractmethod def get(self, id: str) - User | None: Get user by ID. abstractmethod def save(self, user: User) - None: Save user. Protocol 仅用于结构化鸭子类型from typing import Protocol class Drawable(Protocol): def draw(self) - None: ... def render(obj: Drawable) - None: obj.draw()Dignified Python 的立场是优先 ABC因为它让这是一个接口这件事在类定义处一目了然。完整示例把上面所有语法串起来泛型 StackPEP 695 Self T | Nonefrom typing import Self class Stack[T]: Type-safe stack with PEP 695 syntax. def __init__(self) - None: self._items: list[T] [] def push(self, item: T) - Self: Push item and return self for chaining. self._items.append(item) return self def pop(self) - T | None: Pop item or return None if empty. if not self._items: return None return self._items.pop() def peek(self) - T | None: Peek at top item without removing. if not self._items: return None return self._items[-1] def is_empty(self) - bool: Check if stack is empty. return len(self._items) 0 # 使用 numbers Stack[int]() numbers.push(1).push(2).push(3) top numbers.pop() # 类型检查器知道这是 int | None泛型 RepositoryPEP 695 ABC Selffrom abc import ABC, abstractmethod from typing import Self class Repository[T]: Abstract repository with generic type parameter. abstractmethod def get(self, id: str) - T | None: Get entity by ID. abstractmethod def save(self, entity: T) - Self: Save entity, return self for chaining. abstractmethod def delete(self, id: str) - bool: Delete entity, return success. def get_or_fail(self, id: str) - T: Get entity or raise error. entity self.get(id) if entity is None: raise ValueError(fEntity not found: {id}) return entity class InMemoryRepositoryT: In-memory repository implementation. def __init__(self) - None: self._storage: dict[str, T] {} def get(self, id: str) - T | None: return self._storage.get(id) def save(self, entity: T) - Self: # 假设 entity 有 id 属性 entity_id str(getattr(entity, id, id(entity))) self._storage[entity_id] entity return self def delete(self, id: str) - bool: if id in self._storage: del self._storage[id] return True return False # 使用 from dataclasses import dataclass dataclass class User: id: str name: str repo InMemoryRepository[User]() repo.save(User(1, Alice)).save(User(2, Bob)) user repo.get(1) # 类型User | None泛型 BuilderPEP 695 流式接口from typing import Self class QueryBuilder[T]: Generic query builder with fluent interface. def __init__(self, result_type: type[T]) - None: self._result_type result_type self._filters: list[str] [] self._limit: int | None None def filter(self, condition: str) - Self: Add filter condition. self._filters.append(condition) return self def limit(self, n: int) - Self: Set result limit. self._limit n return self def build(self) - str: Build query string. query AND .join(self._filters) if self._limit: query f LIMIT {self._limit} return query # 使用 builder QueryBuilderUser query ( builder .filter(active true) .filter(age 18) .limit(10) .build() )泛型函数工具多类型参数 Callablefrom collections.abc import Callable def map_listT, U - list[U]: Map function over list items. return [func(item) for item in items] def filter_listT - list[T]: Filter list by predicate. return [item for item in items if predicate(item)] def reduce_listT, U - U: Reduce list to single value. result initial for item in items: result func(result, item) return result # 使用 numbers [1, 2, 3, 4, 5] doubled map_list(numbers, lambda x: x * 2) # list[int] evens filter_list(numbers, lambda x: x % 2 0) # list[int] sum_val reduce_list(numbers, lambda acc, x: acc x, 0) # int类型标注规则什么必须标、什么可以省✅ 必须标注MUST所有公开函数的参数self、cls除外所有公开函数的返回值所有类属性公有与私有模块级常量 应当标注SHOULD内部函数签名复杂局部变量 可以省略MAY类型显然的简单局部变量如count 0短行内 lambda 的参数短推导式中的循环变量运行类型检查器ty 与 docling 的实际配置技能文档给出的运行命令与配置方式uv run ty check所有代码都应无错误地通过类型检查。ty在pyproject.toml中的最小配置[tool.ty.environment] python-version 3.12docling 仓库的真实配置是一个很好的对照样本。从 pyproject.toml 可以看到依赖组typecheck固定了ty0.0.33以及types-setuptools、pandas-stubs、types-openpyxl、types-requests、boto3-stubs等第三方类型存根[tool.ty.rules]中all warn即所有诊断默认降级为警告[tool.ty.environment]的python-version 3.10——因为 docling 的requires-python从 3.10 起步检查器必须按 3.10 的语义来分析代码因此 docling 源码中不会出现 PEP 695 语法[tool.ty.analysis]的allowed-unresolved-imports列出了transformers.**、torchvision.**、easyocr.**等一批没有完整类型存根的可选依赖使缺失存根的第三方库不报错。这里可以推断出技能文档与项目配置的分工python-version 3.12是当且仅当项目最低版本为 3.12时的取值docling 把同一配置项设为3.10以匹配自身支持范围。常见模式None 安全检查先检查再使用def process_user(user: User | None) - str: if user is None: return No user return user.namedict.get()的类型安全处理def get_port(config: dict[str, int]) - int: port config.get(port) if port is None: return 8080 return port列表取首元素def first_or_defaultT - T: if not items: return default return items[0]这三个模式体现了 Dignified Python 的 LBYLLook Before You Leap倾向在访问前显式判断而不是依赖异常兜底。PEP 695 与 TypeVar 的取舍决策用 PEP 695 的场景无约束/无边界的简单泛型函数简单泛型类绝大多数常见泛型用法所有新代码仍须用 TypeVar 的场景受限类型变量TypeVar(T, str, bytes)复杂边界的 bound 类型变量协变/逆变类型变量需要在多个函数间复用同一个 TypeVar从 Python 3.11 迁移到 3.12升级步骤源自原文档迁移到 PEP 695 语法TypeVardef func(x: T) - T→def funcT - TGeneric[T]class C(Generic[T])→class C[T]用type语句替换别名Config dict[str, str]→type Config dict[str, str]约束型 TypeVar 保留不动带 constraints 的TypeVar依然必需3.11 的所有旧语法继续有效Self仍然优先|联合仍然优先docling 提供了一个反方向的现实参照它从 3.10 起步、支持到 3.14所以整个代码库停留在TypeVar/Generic层级的写法并且用 ruff 的UP规则保证List/Dict/Optional这类更旧的写法不回流。如果你的项目最低版本是 3.12则可以一步到位采用本文的 PEP 695 规范如果像 docling 一样要向下兼容则把本文当作目标语法储备在当前可用范围内使用内建泛型、|联合与Self即可。小结python-3.12.md的价值在于把 Python 3.12 类型注解的该选哪个问题压缩成一张决策表集合用内建泛型、联合用|、Optional 用X | None、自返回用Self、简单泛型用 PEP 695、约束泛型用TypeVar、别名用type语句、接口用 ABC、Callable从collections.abc导入最后用uv run ty check闭环验证。配合 docling 仓库中真实的ty配置与大量Generic[T]/TYPE_CHECKING用法这套规范既适合 3.12 的新项目直接落地也为兼容旧版本的项目提供了清晰的演进路线。【免费下载链接】doclingGet your documents ready for gen AI项目地址: https://gitcode.com/GitHub_Trending/do/docling创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考