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 是字符串,返回浮点数
类型注解的好处¶
- 自动补全:IDE(如 VS Code、PyCharm)能根据类型给你更精准的代码提示
- 减少 bug:用 mypy 等工具可以在运行前发现类型错误
- 当文档用:新同事看函数签名就知道怎么调用
- 面试加分:说明你有工程素养,不是只会写"能跑就行"的代码
重要:类型注解只是"标签",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 倍。
安装¶
基本使用¶
# 检查单个文件
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¶
原因:函数标注了返回类型,但某些分支没有 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'¶
原因:pyproject.toml 中指定了 hatchling 作为构建后端,但没安装。
解决:
3. ruff 报错:F401 imported but unused¶
原因:导入了模块但没用到。
解决:删掉没用的 import 行。或者用 ruff check --fix 自动修复。
4. ModuleNotFoundError:安装后 import 不了¶
原因: - 没有执行 pip install -e . - 或者 src/ 目录结构不对(缺少 __init__.py) - 或者包名和目录名不一致
解决:
# 检查目录结构
ls src/metagenome_tools/__init__.py # 确认 __init__.py 存在
# 重新安装
pip install -e .
# 检查安装位置
pip show metagenome-tools # 查看包信息
速查表¶
类型注解速查¶
| 类型 | 写法 | 说明 |
|---|---|---|
| 整数 | int | count: int = 0 |
| 浮点数 | float | abundance: float = 0.01 |
| 字符串 | str | name: str = "E.coli" |
| 布尔 | bool | is_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 格式化通过