跳转至

569 Python 类型注解与包发布

类型注解(Type Hints)

为什么要加类型注解?

白话解释:类型注解就是给函数的参数和返回值加说明书

想象你写了一个函数叫 calculate_diversity(data, method),过了三个月你自己都不记得:data 应该传什么?一个列表?一个字典?一个文件路径?method 是字符串还是数字?返回值是什么?

类型注解就是在代码里直接标注清楚:

# 没有类型注解 — 看不出参数类型,像猜谜
def calculate_diversity(data, method):
    pass

# 有类型注解 — 一目了然
def calculate_diversity(data: dict[str, float], method: str) -> float:
    pass
# 白话:data 是一个字典(key 是字符串,value 是浮点数),method 是字符串,返回浮点数

类型注解的好处

  1. 自动补全:IDE(如 VS Code、PyCharm)能根据类型给你更精准的代码提示
  2. 减少 bug:用 mypy 等工具可以在运行前发现类型错误
  3. 当文档用:新同事看函数签名就知道怎么调用
  4. 面试加分:说明你有工程素养,不是只会写"能跑就行"的代码

重要:类型注解只是"标签",Python 不会在运行时强制检查。你标了 x: int,传个字符串进去 Python 也不会报错。要想真正检查,需要用 mypy 这样的静态类型检查器。

基础语法

# === 变量注解 ===
sample_name: str = "T2D_001"             # 字符串
read_count: int = 1500000                # 整数
abundance: float = 0.0342                # 浮点数
is_healthy: bool = False                 # 布尔值

# === 容器类型(Python 3.9+,直接用小写 list/dict/tuple) ===
species_list: list[str] = ["E.coli", "B.fragilis"]       # 字符串列表
abundance_map: dict[str, float] = {"E.coli": 0.15}       # 字符串→浮点数的字典
coordinates: tuple[float, float] = (3.14, 2.72)          # 两个浮点数的元组

# === Python 3.10+ 的新语法:用 | 替代 Union ===
sample_id: str | int = "S001"            # 可以是字符串或整数
threshold: float | None = None           # 可以是浮点数或 None

函数签名注解

def filter_species(
    abundance_table: dict[str, float],   # 参数:物种→丰度的字典
    threshold: float = 0.01,             # 参数:阈值,默认 0.01
) -> dict[str, float]:                   # 返回值:过滤后的字典
    """过滤低丰度物种"""
    return {
        species: abd                     # 保留丰度 >= 阈值的物种
        for species, abd in abundance_table.items()
        if abd >= threshold
    }


def read_fastq(filepath: str) -> list[tuple[str, str, str]]:
    """
    读取 FASTQ 文件

    返回:[(header, sequence, quality), ...] 的列表
    """
    records: list[tuple[str, str, str]] = []  # 显式标注局部变量类型
    # ... 读取逻辑 ...
    return records


def count_reads(filepath: str) -> int | None:
    """计算 reads 数,文件不存在返回 None"""
    # ...
    pass

复杂类型

from typing import Callable, Sequence   # 从 typing 模块导入复杂类型
from collections.abc import Iterator    # Python 3.9+ 推荐从 collections.abc 导入

# Sequence:只读的序列(比 list 更宽泛,也接受 tuple)
def mean_abundance(values: Sequence[float]) -> float:
    """计算平均丰度"""
    return sum(values) / len(values) if values else 0.0


# Callable:可调用对象(函数类型)
# Callable[[参数类型], 返回类型]
def apply_filter(
    data: dict[str, float],
    filter_func: Callable[[float], bool],  # 接收一个浮点数、返回布尔值的函数
) -> dict[str, float]:
    """对丰度表应用自定义过滤函数"""
    return {k: v for k, v in data.items() if filter_func(v)}


# Iterator:迭代器(惰性生成,节省内存)
def fastq_records(filepath: str) -> Iterator[tuple[str, str, str]]:
    """逐条生成 FASTQ 记录(适合大文件)"""
    with open(filepath) as f:
        while True:
            header = f.readline().strip()
            if not header:
                break
            seq = f.readline().strip()       # 序列行
            f.readline()                     # + 行(跳过)
            qual = f.readline().strip()      # 质量行
            yield (header, seq, qual)        # yield 产出一条记录


# TypedDict:给字典的每个 key 定义类型
from typing import TypedDict

class KrakenResult(TypedDict):
    """Kraken2 分类结果的类型定义"""
    name: str                   # 物种名
    percentage: float           # 丰度百分比
    reads: int                  # reads 数
    taxid: str                  # NCBI TaxID

typing 模块 vs Python 3.10+ 内置语法

功能旧写法(typing 模块)新写法(3.10+)
可选类型Optional[str]str \| None
联合类型Union[str, int]str \| int
列表List[str]list[str](3.9+)
字典Dict[str, int]dict[str, int](3.9+)
元组Tuple[int, str]tuple[int, str](3.9+)
集合Set[str]set[str](3.9+)

建议:如果你的 Python >= 3.10,优先用新语法,更简洁。如果需要兼容旧版本,用 typing 模块。


实战:给生信函数加类型注解

丰度表处理函数

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""带类型注解的丰度表处理函数"""


def load_abundance_table(filepath: str) -> dict[str, float]:
    """
    加载 TSV 格式的丰度表

    参数:
        filepath: 丰度表文件路径(TSV 格式,第 1 列物种名,第 2 列丰度)

    返回:
        物种名 → 丰度值的字典
    """
    result: dict[str, float] = {}           # 初始化空字典

    with open(filepath, encoding="utf-8") as f:
        for line in f:
            parts = line.strip().split("\t")  # 按 tab 分割
            if len(parts) >= 2:
                species = parts[0]            # 第 1 列:物种名
                abundance = float(parts[1])   # 第 2 列:丰度
                result[species] = abundance

    return result


def filter_by_threshold(
    table: dict[str, float],         # 输入:丰度表
    threshold: float = 0.01,         # 阈值,默认 1%
) -> dict[str, float]:               # 返回:过滤后的丰度表
    """过滤低丰度物种"""
    return {
        species: abd
        for species, abd in table.items()
        if abd >= threshold
    }


def top_n_species(
    table: dict[str, float],         # 输入:丰度表
    n: int = 10,                     # 返回前 N 个
) -> list[tuple[str, float]]:       # 返回:(物种名, 丰度) 的列表
    """获取丰度最高的前 N 个物种"""
    sorted_items = sorted(
        table.items(),               # 把字典转成 (key, value) 列表
        key=lambda x: x[1],         # 按 value(丰度)排序
        reverse=True,                # 从大到小
    )
    return sorted_items[:n]          # 取前 N 个

FASTQ 解析函数

from collections.abc import Iterator   # 导入迭代器类型


def parse_fastq(filepath: str) -> Iterator[tuple[str, str, str]]:
    """
    逐条解析 FASTQ 文件(适合大文件,不会一次全部读入内存)

    参数:
        filepath: FASTQ 文件路径

    生成:
        (header, sequence, quality) 三元组
    """
    with open(filepath, encoding="utf-8") as f:
        while True:
            header = f.readline().strip()    # 第 1 行:序列头(以 @ 开头)
            if not header:                   # 文件结束
                break
            sequence = f.readline().strip()  # 第 2 行:DNA 序列
            f.readline()                     # 第 3 行:+ 号(跳过)
            quality = f.readline().strip()   # 第 4 行:质量值
            yield (header, sequence, quality)


def count_reads_and_bases(filepath: str) -> tuple[int, int]:
    """
    统计 FASTQ 文件的 reads 数和碱基总数

    返回:
        (reads_count, total_bases) 元组
    """
    reads_count: int = 0       # reads 计数
    total_bases: int = 0       # 碱基总数

    for header, seq, qual in parse_fastq(filepath):
        reads_count += 1       # 每条记录 +1
        total_bases += len(seq) # 累加序列长度

    return (reads_count, total_bases)


def gc_content(sequence: str) -> float:
    """
    计算单条序列的 GC 含量

    参数:
        sequence: DNA 序列字符串

    返回:
        GC 含量(0.0 ~ 1.0)
    """
    if not sequence:           # 空序列返回 0
        return 0.0
    gc_count: int = sequence.upper().count("G") + sequence.upper().count("C")
    return gc_count / len(sequence)

统计分析函数

import math  # 数学函数


def shannon_diversity(abundances: list[float]) -> float:
    """
    计算 Shannon 多样性指数

    参数:
        abundances: 各物种的相对丰度列表(值在 0~1 之间,总和为 1)

    返回:
        Shannon 指数 H'
    """
    h: float = 0.0                          # 初始化 Shannon 指数

    for p in abundances:                    # 遍历每个物种的丰度
        if p > 0:                           # 丰度为 0 的跳过(log(0) 无意义)
            h -= p * math.log(p)            # H' = -Σ(pi * ln(pi))

    return h


def simpson_diversity(abundances: list[float]) -> float:
    """
    计算 Simpson 多样性指数(1 - D)

    参数:
        abundances: 各物种的相对丰度列表

    返回:
        Simpson 指数(0~1,越大多样性越高)
    """
    d: float = sum(p ** 2 for p in abundances)  # D = Σ(pi^2)
    return 1.0 - d                               # 1 - D

mypy 静态类型检查

mypy 是 Python 官方维护的静态类型检查器。最新版本是 mypy 2.0.0(2026 年 5 月 6 日发布),支持并行类型检查,性能提升高达 5 倍。

安装

pip install mypy               # 安装 mypy(当前最新版 2.0.0)
mypy --version                 # 检查版本
# 输出:mypy 2.0.0 (compiled: yes)

基本使用

# 检查单个文件
mypy my_script.py              # 检查 my_script.py 中的类型错误

# 检查整个目录
mypy src/                      # 检查 src 目录下所有 .py 文件

# 严格模式(推荐正式项目使用)
mypy --strict my_script.py     # 强制所有函数都要有类型注解

# 并行检查(mypy 2.0 新功能,适合大项目)
mypy --num-workers 4 src/      # 用 4 个进程并行检查

常见错误示例及修复

# === 错误 1:类型不匹配 ===
def add_counts(a: int, b: int) -> int:
    return a + b

result = add_counts("10", "20")  # mypy 报错:Argument 1 has type "str"; expected "int"
# 修复:传入正确类型
result = add_counts(10, 20)

# === 错误 2:返回值类型不对 ===
def get_species_name(taxid: int) -> str:
    if taxid == 0:
        return None  # mypy 报错:返回值应该是 str,但这里返回了 None
    return "E.coli"
# 修复:把返回类型改为 str | None
def get_species_name(taxid: int) -> str | None:
    if taxid == 0:
        return None
    return "E.coli"

# === 错误 3:可能为 None 的值未检查 ===
def process(name: str | None) -> str:
    return name.upper()  # mypy 报错:name 可能是 None,None 没有 upper 方法
# 修复:先检查是否为 None
def process(name: str | None) -> str:
    if name is None:
        return ""
    return name.upper()

mypy 配置文件

在项目根目录创建 mypy.ini 或在 pyproject.toml 中配置(推荐后者):

# pyproject.toml 中的 mypy 配置
[tool.mypy]
python_version = "3.12"          # 目标 Python 版本
warn_return_any = true           # 返回 Any 时警告
warn_unused_ignores = true       # 未使用的 # type: ignore 警告
disallow_untyped_defs = true     # 禁止没有类型注解的函数
ignore_missing_imports = true    # 忽略没有类型存根的第三方库

Python 包开发

为什么要打包?

白话:把你写的多个 .py 文件打包成一个"软件包",这样别人(或者未来的你)只需要 pip install 一下就能用了,不用到处复制文件。

项目结构(src layout,现代推荐)

metagenome-tools/                  # 项目根目录
├── pyproject.toml                 # 项目配置文件(核心!)
├── README.md                      # 项目说明
├── LICENSE                        # 开源许可证
├── src/                           # 源代码目录
│   └── metagenome_tools/          # 包目录(注意:用下划线不用横线)
│       ├── __init__.py            # 包初始化文件
│       ├── abundance.py           # 丰度处理模块
│       ├── diversity.py           # 多样性计算模块
│       └── kraken.py              # Kraken 报告解析模块
└── tests/                         # 测试目录
    ├── test_abundance.py
    ├── test_diversity.py
    └── test_kraken.py

__init__.py 和模块组织

__init__.py 是包的"入口文件",当别人 import metagenome_tools 时,Python 最先执行这个文件。

# src/metagenome_tools/__init__.py
"""
metagenome_tools - 宏基因组数据分析工具集

包含:丰度过滤、多样性计算、Kraken 报告解析等功能
"""

__version__ = "0.1.0"  # 包版本号

# 把常用函数暴露出来,方便用户直接 import
from metagenome_tools.abundance import filter_by_threshold, top_n_species
from metagenome_tools.diversity import shannon_diversity, simpson_diversity
from metagenome_tools.kraken import parse_kraken2_report

pyproject.toml(现代标准)

pyproject.toml 是 Python 包的唯一配置文件(取代了以前的 setup.py + setup.cfg + requirements.txt)。PEP 621 标准化了 [project] 表,是 2026 年的绝对主流。

# pyproject.toml — 完整示例

# ============================================================
# 1. 构建系统配置
# ============================================================
[build-system]
requires = ["hatchling"]               # 构建后端(hatchling 轻量好用)
build-backend = "hatchling.build"      # 指定用 hatchling 来构建包

# ============================================================
# 2. 项目元数据(PEP 621 标准)
# ============================================================
[project]
name = "metagenome-tools"              # 包名(pip install metagenome-tools)
version = "0.1.0"                      # 版本号
description = "宏基因组数据分析工具集"  # 简短描述
readme = "README.md"                   # 详细说明文件
license = "MIT"                        # 开源许可证
requires-python = ">=3.10"            # 最低 Python 版本
authors = [
    { name = "彭文强", email = "your@email.com" },
]
keywords = ["metagenomics", "bioinformatics", "microbiome"]

# 运行时依赖
dependencies = [
    "pandas>=2.0",                     # 数据处理
    "numpy>=1.24",                     # 数值计算
]

# 可选依赖(pip install metagenome-tools[dev])
[project.optional-dependencies]
dev = [
    "pytest>=7.0",                     # 测试框架
    "mypy>=2.0",                       # 类型检查
    "ruff>=0.15",                      # 代码检查+格式化
    "black>=26.1",                     # 代码格式化
]

# CLI 入口点:安装后可以直接在终端使用的命令
[project.scripts]
filter-abundance = "metagenome_tools.abundance:main"   # 丰度过滤命令
parse-kraken = "metagenome_tools.kraken:main"          # Kraken 解析命令

# 项目链接
[project.urls]
Homepage = "https://github.com/yourname/metagenome-tools"
Documentation = "https://github.com/yourname/metagenome-tools#readme"
Issues = "https://github.com/yourname/metagenome-tools/issues"

# ============================================================
# 3. 工具配置(各工具都可以在这里配置)
# ============================================================

# mypy 配置
[tool.mypy]
python_version = "3.12"
warn_return_any = true
disallow_untyped_defs = true
ignore_missing_imports = true

# ruff 配置
[tool.ruff]
target-version = "py312"               # 目标 Python 版本
line-length = 88                       # 每行最大长度

[tool.ruff.lint]
select = ["E", "F", "W", "I", "N"]    # 启用的规则集
# E=pycodestyle 错误, F=pyflakes, W=警告, I=isort, N=命名

# black 配置
[tool.black]
target-version = ["py312"]
line-length = 88

# pytest 配置
[tool.pytest.ini_options]
testpaths = ["tests"]                  # 测试文件目录

可编辑安装

# 在项目根目录执行(metagenome-tools/)
pip install -e .                       # -e 表示可编辑安装
# 白话:安装后你改了代码,不用重新安装就能生效
# 这在开发阶段非常方便

# 安装包含开发依赖
pip install -e ".[dev]"                # 同时安装 pytest、mypy、ruff 等

# 验证安装成功
python -c "import metagenome_tools; print(metagenome_tools.__version__)"
# 输出:0.1.0

# 如果配置了 [project.scripts],命令行工具也可以直接用了
filter-abundance --help
parse-kraken --help

实战:把生信工具打包

各模块代码

src/metagenome_tools/abundance.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""丰度表处理模块"""

import argparse  # 命令行参数解析
import csv       # CSV 读写
import sys       # 系统相关


def load_table(filepath: str) -> dict[str, float]:
    """加载 TSV 丰度表"""
    result: dict[str, float] = {}
    with open(filepath, encoding="utf-8") as f:
        reader = csv.reader(f, delimiter="\t")
        for row in reader:
            if len(row) >= 2:
                try:
                    result[row[0]] = float(row[1])
                except ValueError:
                    continue
    return result


def filter_by_threshold(
    table: dict[str, float],
    threshold: float = 0.01,
) -> dict[str, float]:
    """过滤低丰度物种"""
    return {k: v for k, v in table.items() if v >= threshold}


def top_n_species(
    table: dict[str, float],
    n: int = 10,
) -> list[tuple[str, float]]:
    """获取丰度最高的前 N 个物种"""
    return sorted(table.items(), key=lambda x: x[1], reverse=True)[:n]


def main() -> None:
    """CLI 入口函数"""
    parser = argparse.ArgumentParser(description="丰度表过滤工具")
    parser.add_argument("--input", "-i", required=True, help="输入丰度表")
    parser.add_argument("--threshold", "-t", type=float, default=0.01, help="阈值")
    parser.add_argument("--output", "-o", help="输出文件")
    args = parser.parse_args()

    table = load_table(args.input)
    filtered = filter_by_threshold(table, args.threshold)

    out = open(args.output, "w", encoding="utf-8") if args.output else sys.stdout
    writer = csv.writer(out, delimiter="\t")
    for species, abd in sorted(filtered.items(), key=lambda x: x[1], reverse=True):
        writer.writerow([species, f"{abd:.6f}"])
    if args.output:
        out.close()

src/metagenome_tools/diversity.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""多样性指数计算模块"""

import math  # 数学函数


def shannon_diversity(abundances: list[float]) -> float:
    """计算 Shannon 多样性指数 H'"""
    h: float = 0.0
    for p in abundances:
        if p > 0:
            h -= p * math.log(p)
    return h


def simpson_diversity(abundances: list[float]) -> float:
    """计算 Simpson 多样性指数(1 - D)"""
    return 1.0 - sum(p ** 2 for p in abundances)


def observed_species(abundances: list[float]) -> int:
    """计算观察到的物种数(丰度 > 0 的物种数量)"""
    return sum(1 for p in abundances if p > 0)


def pielou_evenness(abundances: list[float]) -> float:
    """计算 Pielou 均匀度指数 J'"""
    s = observed_species(abundances)     # 物种数
    if s <= 1:                           # 只有 0 或 1 个物种没法算均匀度
        return 0.0
    h = shannon_diversity(abundances)    # Shannon 指数
    return h / math.log(s)              # J' = H' / ln(S)

代码规范工具

Black:代码格式化

Black(当前最新版 26.3.1)是"不妥协的"Python 代码格式化器——它帮你统一代码风格,不给你选项,减少团队争吵。

pip install black                       # 安装 Black

# 格式化单个文件
black my_script.py                      # 自动格式化

# 格式化整个目录
black src/                              # 格式化 src/ 下所有 .py 文件

# 只检查不修改(适合 CI)
black --check src/                      # 有不规范的会返回非零退出码

# 查看会改什么(不实际修改)
black --diff my_script.py               # 显示 diff

Ruff:代码检查(Linting)

Ruff(当前最新版 v0.15.1,2026 年 2 月发布)是用 Rust 编写的超快速 Python linter,可以替代 Flake8 + isort + pyupgrade + autoflake 等一堆工具,速度快几十到几百倍。

pip install ruff                        # 安装 Ruff

# 检查代码问题
ruff check src/                         # 检查 src/ 目录

# 自动修复可修复的问题
ruff check --fix src/                   # 自动修复

# 格式化代码(Ruff 也能格式化,可替代 Black)
ruff format src/                        # 格式化

# 查看所有可用规则
ruff rule --all                         # 列出 900+ 条规则

pyproject.toml 中配置 Ruff 和 Black

# 在 pyproject.toml 中统一配置

[tool.ruff]
target-version = "py312"
line-length = 88                        # 和 Black 保持一致

[tool.ruff.lint]
select = [
    "E",     # pycodestyle 错误
    "F",     # pyflakes
    "W",     # pycodestyle 警告
    "I",     # isort(import 排序)
    "N",     # pep8-naming(命名规范)
    "UP",    # pyupgrade(升级旧语法)
    "B",     # flake8-bugbear(常见 bug)
]
ignore = [
    "E501",  # 行太长(已经用 line-length 控制了)
]

[tool.ruff.lint.isort]
known-first-party = ["metagenome_tools"]  # 你自己的包名

[tool.black]
target-version = ["py312"]
line-length = 88

推荐开发流程

# 1. 写完代码后,先格式化
black src/ tests/                       # 或者用 ruff format

# 2. 再检查代码质量
ruff check src/ tests/                  # 检查问题

# 3. 然后做类型检查
mypy src/                              # 检查类型错误

# 4. 最后跑测试
pytest tests/                           # 运行测试

发布到 PyPI(了解流程即可)

PyPI(Python Package Index)是 Python 的官方包仓库,pip install 默认从这里下载包。

发布流程概览

# 第 1 步:安装构建和上传工具
pip install build twine                 # build 用于打包,twine 用于上传

# 第 2 步:构建包
python -m build                         # 在项目根目录执行
# 会生成 dist/ 目录,里面有 .tar.gz 和 .whl 文件

# 第 3 步:先发到测试 PyPI(练手用)
twine upload --repository testpypi dist/*

# 第 4 步:确认没问题后,发到正式 PyPI
twine upload dist/*

# 第 5 步:别人就能安装了
pip install metagenome-tools

面试重点:你不需要真的发到 PyPI,但要知道这个流程。面试官可能会问"你知道怎么发布 Python 包吗?",能答出 pyproject.toml + build + twine 这个流程就足够了。


常见报错

1. mypy 报错:Missing return statement

error: Missing return statement  [return]

原因:函数标注了返回类型,但某些分支没有 return

# 报错代码
def get_name(taxid: int) -> str:
    if taxid > 0:
        return "species"
    # 缺少 else 分支的 return!

# 修复
def get_name(taxid: int) -> str:
    if taxid > 0:
        return "species"
    return "unknown"               # 补上 else 情况

2. pip install -e . 报错:No module named 'hatchling'

ERROR: Could not build wheels for metagenome-tools, ...

原因pyproject.toml 中指定了 hatchling 作为构建后端,但没安装。

解决

pip install hatchling              # 安装构建后端
pip install -e .                   # 重新安装

3. ruff 报错:F401 imported but unused

abundance.py:2:1: F401 `os` imported but unused

原因:导入了模块但没用到。

解决:删掉没用的 import 行。或者用 ruff check --fix 自动修复。

4. ModuleNotFoundError:安装后 import 不了

ModuleNotFoundError: No module named 'metagenome_tools'

原因: - 没有执行 pip install -e . - 或者 src/ 目录结构不对(缺少 __init__.py) - 或者包名和目录名不一致

解决

# 检查目录结构
ls src/metagenome_tools/__init__.py     # 确认 __init__.py 存在

# 重新安装
pip install -e .

# 检查安装位置
pip show metagenome-tools               # 查看包信息

速查表

类型注解速查

类型写法说明
整数intcount: int = 0
浮点数floatabundance: float = 0.01
字符串strname: str = "E.coli"
布尔boolis_valid: bool = True
列表list[str]["a", "b"]
字典dict[str, float]{"E.coli": 0.15}
元组tuple[int, str](1, "hello")
可选str \| None可能是 str 也可能是 None
联合int \| str可能是 int 也可能是 str
函数Callable[[int], str]接受 int 返回 str 的函数
迭代器Iterator[str]yield 产出 str 的函数
任意Any不限制类型(尽量少用)

pyproject.toml 最小模板

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-package"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = []

[project.scripts]
my-command = "my_package.cli:main"

常用命令速查

命令作用
pip install -e .可编辑安装(开发用)
pip install -e ".[dev]"安装 + 开发依赖
mypy src/类型检查
mypy --strict src/严格类型检查
black src/代码格式化
ruff check src/代码检查
ruff check --fix src/自动修复
ruff format src/Ruff 格式化
python -m build构建包
twine upload dist/*上传到 PyPI

包开发检查清单

  • [ ] 使用 src/ layout 目录结构
  • [ ] 每个包目录有 __init__.py
  • [ ] pyproject.toml[build-system][project]
  • [ ] 指定了 requires-python
  • [ ] 列出了所有 dependencies
  • [ ] 配置了 [project.scripts] CLI 入口点
  • [ ] 能通过 pip install -e . 安装
  • [ ] 所有公开函数有类型注解
  • [ ] mypy 检查无错误
  • [ ] ruff/black 格式化通过