python从入门到精通- 第1章: 环境与工具链

2 阅读12分钟

第1章: 环境与工具链

Java 开发者打开 IntelliJ,JDK、Maven、Gradle 一条龙。Python 的世界没有这样一个"大一统"——你需要自己组装工具链。 本章帮你从零搭建一套工业级 Python 开发环境,每个工具都对标你熟悉的 JVM 生态。


1.1 Python 安装与多版本管理 (pyenv)

Java/Kotlin 对比

# JVM 开发者用 SDKMAN 管理多版本 JDK
$ sdk list java
$ sdk install java 21.0.2-tem
$ sdk install java 17.0.9-tem
$ sdk use java 21.0.2-tem       # 切换当前 shell
$ sdk default java 21.0.2-tem   # 设为默认
$ sdk current java               # 查看当前版本
# 或者手动安装多个 JDK,用 JAVA_HOME 切换
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk
export PATH=$JAVA_HOME/bin:$PATH

Python 实现

pyenv 是 Python 生态中最主流的多版本管理工具,对标 SDKMAN。

# === 安装 pyenv (macOS) ===
brew install pyenv

# === 安装 pyenv (Linux - Ubuntu/Debian) ===
# 1. 安装编译依赖
sudo apt update && sudo apt install -y \
    make build-essential libssl-dev zlib1g-dev \
    libbz2-dev libreadline-dev libsqlite3-dev curl \
    libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev

# 2. 安装 pyenv
curl https://pyenv.run | bash

# 3. 配置 shell (加入 ~/.bashrc 或 ~/.zshrc)
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

# 4. 重启 shell
exec "$SHELL"

# === 基本使用 ===
# 查看可安装的版本
pyenv install --list | grep "^\s*3\."

# 安装指定版本(从源码编译)
pyenv install 3.12.3
pyenv install 3.11.9
pyenv install 3.10.14

# 查看已安装的版本
pyenv versions
# * system (set by /home/user/.pyenv/version)
#   3.10.14
#   3.11.9
#   3.12.3

# 切换版本(三种粒度)
pyenv global 3.12.3          # 全局默认
pyenv local 3.11.9           # 当前目录(写入 .python-version 文件)
pyenv shell 3.10.14          # 当前 shell 会话

# 查看当前生效的版本
pyenv version
# 3.12.3 (set by /home/user/.pyenv/version)

# 卸载版本
pyenv uninstall 3.10.14

pyenv 工作原理(和 SDKMAN 类似):

# pyenv 通过修改 PATH 来实现版本切换
# 当你执行 python 时,PATH 中最前面的 pyenv shims 目录会拦截
$ which python
/home/user/.pyenv/shims/python

# shim 脚本读取 .python-version 或全局配置,转发到对应的 Python 安装目录
$ pyenv which python
/home/user/.pyenv/versions/3.12.3/bin/python

pyenv-virtualenv 集成

# 安装 pyenv-virtualenv 插件
brew install pyenv-virtualenv  # macOS
# Linux: git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv

# 在 shell 配置中添加
eval "$(pyenv virtualenv-init -)"

# 用 pyenv 直接创建虚拟环境(绑定到特定 Python 版本)
pyenv virtualenv 3.12.3 my-project-env
pyenv activate my-project-env
pyenv deactivate

核心差异

维度SDKMAN (JVM)pyenv (Python)
安装方式下载预编译二进制从源码编译(需要编译依赖)
版本粒度JDK 版本CPython 版本(还有 PyPy、GraalPy 等)
配置文件~/.sdkman/candidates/java/current.python-version(目录级)
切换机制修改 PATH + 软链接PATH shims 拦截
项目级锁定sdk use java xx(手动).python-version(自动,进入目录生效)
Windows 支持WSL 或直接支持pyenv-win(独立项目)

常见陷阱

# 陷阱1: 忘记安装编译依赖,pyenv install 失败
# 错误: BUILD FAILED (Ubuntu)
# 解决: 安装上面列出的 build-essential, libssl-dev 等依赖

# 陷阱2: macOS 升级后 pyenv install 失败
# 错误: xcrun: error: invalid active developer path
# 解决: xcode-select --install

# 陷阱3: pyenv global 改了但 python --version 没变
# 原因: shell 缓存了旧路径
# 解决: hash -r 或开新 shell

# 陷阱4: 在已有虚拟环境中切换 Python 版本
# pyenv 切换的是系统级 Python,不影响已创建的虚拟环境
# 虚拟环境创建时绑定了特定的 Python 解释器

何时使用

  • 必须用 pyenv: 需要在多个 Python 版本间切换(维护老项目 3.8,新项目 3.12)
  • 可以不用: 只用一个 Python 版本,且系统包管理器提供的版本够用(如 macOS brew install python@3.12
  • 替代方案: asdf(多语言版本管理器,同时管理 Node.js、Python、Java 等)、conda(自带 Python 版本管理)

1.2 虚拟环境: venv, conda, poetry, uv

Java/Kotlin 对比

// Java 没有虚拟环境的概念
// Java 靠 classpath 隔离:不同项目用不同的依赖 jar
// 但所有项目共享同一个 JDK 运行时

// Maven/Gradle 的依赖范围(scope)提供了有限的隔离:
// - compile: 编译和运行都可用
// - test: 仅测试可用
// - provided: 编译可用,运行时由容器提供

// Java 的"隔离"本质上是编译期依赖管理,不是运行时环境隔离
// 两个项目依赖同一个库的不同版本?只能靠 Maven 的"最近优先"策略解决
// Kotlin 同样依赖 JVM 的 classpath 机制
// Gradle 的 configuration 提供类似隔离:
// implementation, api, testImplementation, compileOnly
// 但仍然是 classpath 级别的隔离,不是环境隔离

关键认知差异: Java/Kotlin 的依赖隔离是"编译期声明 + classpath 组装",Python 的虚拟环境是"运行时环境克隆 + 独立的包安装目录"。Python 的隔离更彻底——连解释器本身都可以不同版本。

Python 实现

venv — 标准库内置(推荐大多数场景)
# 创建虚拟环境(Python 3.10+ 内置,无需安装)
python3 -m venv .venv

# 激活
source .venv/bin/activate      # Linux/macOS
# .venv\Scripts\activate       # Windows (PowerShell)

# 激活后,python 和 pip 指向虚拟环境
which python
# /path/to/project/.venv/bin/python

python --version
# Python 3.12.3

# 安装包(只装在虚拟环境里)
pip install requests
pip list
# Package    Version
# ---------- -------
# pip        24.0
# requests   2.32.3

# 退出虚拟环境
deactivate
# demo_venv.py — 验证虚拟环境隔离
import sys
import os

print(f"Python 路径: {sys.executable}")
print(f"是否在虚拟环境中: {sys.prefix != sys.base_prefix}")
print(f"site-packages: {[p for p in sys.path if 'site-packages' in p]}")

# 在虚拟环境中运行:
# Python 路径: /path/to/.venv/bin/python
# 是否在虚拟环境中: True
# site-packages: ['/path/to/.venv/lib/python3.12/site-packages']

# 在全局环境中运行:
# Python 路径: /usr/bin/python3
# 是否在虚拟环境中: False
conda — 数据科学生态
# 安装 Miniconda(轻量版,不含默认包)
# https://docs.conda.io/en/latest/miniconda.html
brew install --cask miniconda  # macOS

# 创建环境(conda 自带 Python,不需要系统 Python)
conda create -n ml-project python=3.11
conda activate ml-project

# conda 安装包(从 conda-forge 仓库,不只是 PyPI)
conda install numpy pandas scikit-learn
# 也可以混用 pip
pip install requests

# 导出环境(类似 requirements.txt 但更强大)
conda env export > environment.yml
# 重建环境
conda env create -f environment.yml

# conda 的独特优势: 可以管理非 Python 依赖
conda install cudatoolkit=11.8  # CUDA 工具包
poetry — 项目管理一体化
# 安装 poetry
curl -sSL https://install.python-poetry.org | python3 -

# 创建项目(自动创建虚拟环境)
poetry new my-project
cd my-project

# 在已有项目中初始化
cd existing-project
poetry init

# poetry 自动管理虚拟环境(存储在 ~/.cache/pypoetry/)
poetry env use 3.12
poetry env list
poetry env remove 3.11

# 安装依赖(自动创建/使用虚拟环境)
poetry install

# 在虚拟环境中运行命令
poetry run python main.py
poetry run pytest
uv — 2024+ 最快选择
# 安装 uv(Rust 实现,极快)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 或: brew install uv
# 或: pip install uv

# 创建虚拟环境(比 venv 快 10-100 倍)
uv venv
uv venv .venv --python 3.12

# 激活(和 venv 一样)
source .venv/bin/activate

# uv 还可以直接运行命令,无需激活
uv run python main.py
uv run pytest

核心差异

工具速度适合场景依赖管理Python 版本管理
venv慢(秒级)通用开发需配合 pip需配合 pyenv
conda中等数据科学/MLconda + pip 混用内置
poetry中等库开发/应用内置(lock 文件)需配合 pyenv
uv极快(毫秒级)任何场景内置(兼容 pip)内置

常见陷阱

# 陷阱1: 忘记激活虚拟环境就 pip install
# 结果: 包装到了全局环境,污染系统
# 防御: 在 ~/.bashrc 中设置
export PIP_REQUIRE_VIRTUALENV=true
# 这样在虚拟环境外执行 pip 会报错

# 陷阱2: 把 .venv 提交到 git
# 解决: .gitignore 中添加 .venv/

# 陷阱3: 在不同 Python 版本间复用虚拟环境
# 虚拟环境绑定创建时的 Python 版本,不能跨版本复用
# 切换 Python 版本后必须重建虚拟环境

# 陷阱4: conda 和 pip 混用的依赖冲突
# conda install 后再 pip install 可能覆盖 conda 安装的包
# 建议: 先 conda install,再 pip install,不要反过来

何时使用

场景推荐工具
通用 Python 开发uvvenv
数据科学 / MLconda(管理 CUDA 等非 Python 依赖)
库开发(要发布到 PyPI)poetry
CI/CD 环境uv(速度最快)
需要精确锁定依赖poetry(poetry.lock)或 uv(uv.lock)

1.3 包管理: pip vs Poetry vs uv vs pip-tools

Java/Kotlin 对比

<!-- Maven: pom.xml 声明依赖,Maven Central 仓库 -->
<dependencies>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>33.0.0-jre</version>
    </dependency>
</dependencies>
<!-- 传递依赖自动解析,版本冲突靠"最近优先"或 <dependencyManagement> -->
// Gradle: build.gradle.kts 声明依赖
dependencies {
    implementation("com.google.guava:guava:33.0.0-jre")
    // 传递依赖自动解析,冲突靠 Gradle 的解析策略
}
// repositories { mavenCentral() }

关键差异: Maven/Gradle 有传递依赖自动解析版本冲突解决策略。pip 也有传递依赖解析,但历史上较弱(pip 20.3+ 引入了新的依赖解析器大幅改善)。

Python 实现

pip — 基础包管理器
# 安装包
pip install requests
pip install "django>=4.2,<5.0"    # 版本范围
pip install requests==2.32.3       # 精确版本
pip install requests~=2.32         # 兼容版本: >=2.32.0, <2.33.0

# 升级包
pip install --upgrade requests

# 卸载包
pip uninstall requests

# 查看已安装的包
pip list
pip show requests    # 详细信息

# 导出依赖
pip freeze > requirements.txt
# requests==2.32.3
# urllib3==2.2.1
# charset-normalizer==3.3.2
# idna==3.7
# certifi==2024.2.2

# 从文件安装
pip install -r requirements.txt

# pip 的版本说明符(PEP 440)
# ==2.32.3    精确匹配
# >=2.32.0    大于等于
# <3.0        小于
# ~=2.32      兼容版本(最后一位可变)
# !=2.32.0    不等于
# ==2.32.*    通配符匹配
requirements.txt — 传统方式
# requirements.txt — 简单但不够精确
# 直接版本锁定
requests==2.32.3
django>=4.2,<5.0
numpy>=1.24.0

# 带注释的版本
# 生产依赖
flask==3.0.3
gunicorn==22.0.0

# 开发依赖(通常拆分为 requirements-dev.txt)
pytest==8.2.0
ruff==0.4.4
mypy==1.10.1
# requirements.txt 的问题: 没有区分直接依赖和传递依赖
# pip freeze 导出的是所有包(包括传递依赖),版本完全锁定
# 但没有锁文件的概念,不同时间 pip install 可能得到不同结果

# 拆分开发和生产依赖
pip install -r requirements.txt          # 生产
pip install -r requirements-dev.txt      # 开发(包含生产)
pyproject.toml + pip-tools — 编译依赖
# 安装 pip-tools
pip install pip-tools
# requirements.in — 只写直接依赖(类似 pom.xml)
requests>=2.32.0
django>=4.2,<5.0
# 编译为精确的 requirements.txt(类似 Maven 的依赖解析)
pip-compile requirements.in
# 生成 requirements.txt,包含所有传递依赖的精确版本
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile requirements.in
#
asgiref==3.8.1
    # via django
certifi==2024.2.2
    # via requests
charset-normalizer==3.3.2
    # via requests
django==4.2.13
    # via -r requirements.in
requests==2.32.3
    # via -r requirements.in
urllib3==2.2.1
    # via requests
# 同理处理开发依赖
# requirements-dev.in:
pytest>=8.0
mypy>=1.10
-r requirements.in    # 包含生产依赖

pip-compile requirements-dev.in -o requirements-dev.txt
Poetry — 现代依赖管理
# poetry add 添加依赖(自动更新 pyproject.toml 和 poetry.lock)
poetry add requests
poetry add django "^4.2"         # ^ 表示兼容版本: >=4.2.0, <5.0.0
poetry add pytest --group dev    # 开发依赖

poetry add numpy@^1.24          # 指定版本约束

# 安装所有依赖
poetry install
poetry install --with dev       # 包含开发依赖
poetry install --no-dev         # 仅生产依赖

# 更新依赖
poetry update                    # 更新所有
poetry update requests           # 更新指定包

# 查看依赖树
poetry show --tree
# pyproject.toml (Poetry 格式)
[tool.poetry]
name = "my-project"
version = "0.1.0"
description = "My project"
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.32"
django = "^4.2"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0"
mypy = "^1.10"
ruff = "^0.4"

# poetry.lock — 锁文件,保证可复现
# 类似 Maven 的依赖解析结果缓存
# 提交到 git,确保团队和 CI 用完全相同的依赖版本
uv — 2024+ 最快选择
# uv 安装包(兼容 pip 接口,但快 10-100 倍)
uv pip install requests
uv pip install -r requirements.txt
uv pip compile requirements.in -o requirements.txt  # 替代 pip-tools
uv pip sync requirements.txt   # 精确同步环境到 requirements.txt

# uv 的项目管理(类似 poetry)
uv init my-project
cd my-project
uv add requests django
uv add --dev pytest mypy ruff
uv sync          # 安装所有依赖
uv run pytest    # 在虚拟环境中运行
uv lock          # 生成 uv.lock
uv tree          # 查看依赖树
# pyproject.toml (uv 格式)
[project]
name = "my-project"
version = "0.1.0"
description = "My project"
requires-python = ">=3.10"
dependencies = [
    "requests>=2.32.0",
    "django>=4.2,<5.0",
]

[dependency-groups]
dev = [
    "pytest>=8.0",
    "mypy>=1.10",
    "ruff>=0.4",
]

核心差异

维度Maven/GradlepipPoetryuv
依赖声明pom.xml / build.gradlerequirements.txtpyproject.tomlpyproject.toml
锁文件没有(依赖解析缓存)没有poetry.lockuv.lock
传递依赖解析成熟20.3+ 改善成熟成熟
版本冲突解决最近优先 / 强制版本第一个匹配自动解决自动解决
速度慢(JVM 启动)中等极快(Rust)
依赖范围compile/test/provided无(需手动拆分)groupdependency-groups

常见陷阱

# 陷阱1: pip install 没有指定版本,不同时间安装得到不同版本
# 错误:
pip install requests
# 正确: 锁定版本或使用 lock 文件
pip install requests==2.32.3
# 或用 poetry/uv 管理依赖

# 陷阱2: pip install 和 pip freeze 循环依赖
# pip freeze 导出所有包(含传递依赖),下次 pip install -r 可能解析出不同版本
# 解决: 用 pip-tools 或 poetry/uv

# 陷阱3: Poetry 的 ^ 版本约束和 pip 的 >= 不一样
# Poetry: ^4.2 意味着 >=4.2.0, <5.0.0(兼容版本)
# pip: >=4.2 意味着 >=4.2.0,没有上限
# 注意: pyproject.toml 的 [project] dependencies 用 PEP 508 语法,不支持 ^

# 陷阱4: 全局 pip install
# 永远不要 sudo pip install!
# 用 --user 或虚拟环境
pip install --user requests   # 安装到用户目录

何时使用

场景推荐
快速脚本/学习pip + requirements.txt
团队项目/生产环境uvpoetry(有锁文件)
库开发(发布到 PyPI)poetryuv
CI/CDuv(速度优势明显)
数据科学conda + pip
需要精确控制传递依赖pip-toolsuv

1.4 pyproject.toml: 现代项目配置中心 (PEP 621)

Java/Kotlin 对比

<!-- Maven: pom.xml 是项目配置中心 -->
<project>
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0.0</version>
    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <java.version>21</java.version>
    </properties>
    <dependencies>...</dependencies>
    <build>
        <plugins>
            <plugin>  <!-- Checkstyle -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
// Gradle: build.gradle.kts 是项目配置中心
plugins {
    java
    id("com.diffplug.spotless") version "6.25"  // 代码格式化
    id("org.jlleitschuh.gradle.ktlint") version "12.1"  // Kotlin linter
}

group = "com.example"
version = "1.0.0"

java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }

dependencies { implementation("com.google.guava:guava:33.0.0-jre") }

// Spotless 配置
spotless { kotlin { ktlint() } }

Python 实现

pyproject.toml 是 Python 项目的统一配置文件,取代了历史上散落的 setup.pysetup.cfgrequirements.txt.flake8mypy.ini 等文件。

# pyproject.toml — 完整示例(现代 Python 项目配置中心)

# ============================================================
# PEP 621: 项目元数据(替代 setup.py 的 metadata)
# ============================================================
[project]
name = "my-application"
version = "1.0.0"
description = "A modern Python application"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.10"
authors = [
    {name = "Your Name", email = "you@example.com"},
]
classifiers = [
    "Development Status :: 4 - Beta",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "License :: OSI Approved :: MIT License",
]

# 直接依赖(PEP 508 版本说明符)
dependencies = [
    "fastapi>=0.110.0",
    "uvicorn[standard]>=0.29.0",
    "pydantic>=2.6.0",
    "httpx>=0.27.0",
    "structlog>=24.1.0",
]

# 可选依赖组(类似 Maven 的 profile)
[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-cov>=5.0",
    "ruff>=0.4.0",
    "mypy>=1.10",
    "pre-commit>=3.7",
]
docs = [
    "mkdocs>=1.5",
    "mkdocs-material>=9.5",
]
ci = [
    "tox>=4.14",
    "coverage[toml]>=7.5",
]

# 命令行入口点(类似 Java 的 main class)
[project.scripts]
my-app = "my_application.cli:main"

# GUI 入口点
[project.gui-scripts]
my-app-gui = "my_application.gui:main"

# ============================================================
# PEP 518: 构建系统声明
# ============================================================
[build-system]
# 用 hatchling 构建(轻量,推荐应用项目)
requires = ["hatchling"]
build-backend = "hatchling.build"
# 其他选择:
# requires = ["setuptools>=68.0"]        # 传统
# build-backend = "setuptools.build_meta"
# requires = ["flit-core>=3.8"]           # 简单库
# build-backend = "flit_core.build_api"
# requires = ["poetry-core>=1.8"]         # Poetry 项目
# build-backend = "poetry.core.masonry.api"

# ============================================================
# 工具配置集中化
# ============================================================

# --- Ruff: Linter + Formatter ---
[tool.ruff]
target-version = "py310"
line-length = 120
src = ["src"]

[tool.ruff.lint]
select = [
    "E",    # pycodestyle errors
    "W",    # pycodestyle warnings
    "F",    # pyflakes
    "I",    # isort (import 排序)
    "N",    # pep8-naming
    "UP",   # pyupgrade (自动升级语法)
    "B",    # flake8-bugbear (常见 bug 模式)
    "SIM",  # flake8-simplify (简化建议)
    "C4",   # flake8-comprehensions (推导式优化)
    "PTH",  # flake8-use-pathlib (用 pathlib 替代 os.path)
]
ignore = ["E501"]  # 忽略行长度(交给 formatter)

[tool.ruff.lint.isort]
known-first-party = ["my_application"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

# --- mypy: 静态类型检查 ---
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
files = ["src", "tests"]

[[tool.mypy.overrides]]
module = ["httpx.*", "uvicorn.*"]
ignore_missing_imports = true

# --- pytest: 测试框架 ---
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = [
    "-v",
    "--tb=short",
    "--strict-markers",
]
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "integration: marks integration tests",
]

# --- coverage: 代码覆盖率 ---
[tool.coverage.run]
source = ["src"]
branch = true

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "raise NotImplementedError",
    "if __name__ == .__main__.:",
]
fail_under = 80

# --- tox: 多环境测试(类似 Maven 的 CI profile) ---
# [tool.tox]
# legacy_tox_ini = """
# [tox]
# envlist = py310, py311, py312
# [testenv]
# deps = -e.[dev]
# commands = pytest {posargs}
# """

核心差异

维度pom.xml / build.gradle.ktspyproject.toml
标准化Maven/Gradle 各自格式PEP 621 官方标准
工具配置在同一个文件中在同一个文件中([tool.xxx])
构建系统Maven/Gradle 内置可选(hatchling, setuptools, flit, poetry-core)
版本管理SNAPSHOT / RELEASEPEP 440 (1.0.0, 1.0.0a1, 1.0.0rc1, 1.0.0.post1)
多模块Maven multi-module / Gradle subprojectsworkspace(PEP 729, 2024 新增)
仓库声明repositories 块不需要(默认 PyPI,可配置 [tool.uv] 等)

常见陷阱

# 陷阱1: [project] dependencies 的版本语法不是 ^(那是 Poetry 专有)
# 错误:
# dependencies = ["requests ^2.32"]
# 正确:
dependencies = ["requests>=2.32.0"]

# 陷阱2: 混用 setup.py 和 pyproject.toml
# 如果用 pyproject.toml,就不要再创建 setup.py
# 除非你需要自定义构建逻辑(极少需要)

# 陷阱3: [tool.xxx] 的配置格式因工具而异
# [tool.ruff] 用 TOML 原生格式
# [tool.mypy] 用 INI 风格(key = value)
# [tool.pytest.ini_options] 用 pytest 自己的格式
# 具体参考各工具文档

# 陷阱4: PEP 621 的 version 字段不支持动态计算
# 如果需要从 git tag 或 __init__.py 读取版本:
# 用 hatch-vcs 或 setuptools-scm
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"  # 从 git tag 读取版本

何时使用

  • 所有新项目: 必须用 pyproject.toml(PEP 621 标准,2022+ 生态共识)
  • 老项目迁移: 从 setup.py / setup.cfg 迁移到 pyproject.toml
  • 不需要 pyproject.toml 的情况: 单文件脚本、Jupyter notebook

1.5 REPL 与 Jupyter Notebook

Java/Kotlin 对比

// Java 9+ 引入了 JShell,但极少有人用
// 大多数 Java 开发者还是写完整类 → 编译 → 运行
$ jshell
jshell> System.out.println("Hello")
Hello
jshell> int x = 10;
x ==> 10
jshell> x * 2
$2 ==> 20

// 问题: JShell 不支持第三方库、没有可视化、生态薄弱
// Kotlin 有 kotlin REPL,同样极少使用
$ kotlinc
Welcome to Kotlin version 2.0.0
Type :help for help, :quit for quit
>>> println("Hello")
Hello
>>> val x = 10
>>> x * 2
res1: kotlin.Int = 20

Python 实现

Python REPL 基础
# 启动 REPL
python3
# 或指定版本
python3.12
# Python REPL 是日常开发的核心工具(不像 JShell)
# 快速验证想法、调试、学习 API

>>> 2 ** 100
1267650600228229401496703205376

>>> import json
>>> json.dumps({"name": "Alice", "age": 30}, indent=2)
'{\n  "name": "Alice",\n  "age": 30\n}'

>>> [x**2 for x in range(10) if x % 2 == 0]
[0, 4, 16, 36, 64]

>>> def fib(n):
...     a, b = 0, 1
...     for _ in range(n):
...         a, b = b, a + b
...     return a
...
>>> [fib(i) for i in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# 特殊变量
>>> _        # 上一个表达式的结果
34
>>> __       # 倒数第二个
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# 内置帮助
>>> help(str)
>>> help(str.split)
>>> dir(str)          # 列出所有属性和方法
>>> str.__doc__       # 查看文档字符串

# REPL 快捷键
# Ctrl+A  行首
# Ctrl+E  行尾
# Ctrl+R  反向搜索历史
# Ctrl+L  清屏
# Ctrl+C  中断当前命令
# Ctrl+D  退出
IPython — 增强版 REPL
pip install ipython
# IPython 提供的增强功能(Java/Kotlin 生态没有对等物)

# 1. 魔术命令
In [1]: %timeit [x**2 for x in range(1000)]
# 58.2 µs ± 1.2 µs per loop

In [2]: %timeit list(map(lambda x: x**2, range(1000)))
# 87.5 µs ± 2.1 µs per loop

In [3]: %timeit sum(range(1000000))
# 32.1 ms ± 0.5 ms per loop

# 2. Shell 命令
In [4]: !ls -la
In [5]: !pwd
In [6]: files = !ls *.py    # 捕获输出到变量

# 3. 变量检查
In [7]: data = {"name": "Alice", "scores": [90, 85, 92]}
In [8]: data?
# Type:        dict
# String form: {'name': 'Alice', 'scores': [90, 85, 92]}

In [9]: data??   # 显示源码(如果是自定义对象)

# 4. 自动补全(Tab 键)
In [10]: import os
In [11]: os.pa<TAB>    # 自动补全为 os.path

# 5. 历史记录
In [12]: %history
In [13]: %hist -g pattern    # 搜索历史

# 6. 调试
In [14]: %debug    # 进入 post-mortem 调试器
Jupyter Notebook
# 安装 Jupyter
pip install jupyter
# 或用 uv: uv pip install jupyter

# 启动
jupyter notebook
# 或用更现代的 JupyterLab
pip install jupyterlab
jupyter lab
# Jupyter Notebook 的核心价值:
# 1. 代码 + 文档 + 可视化 在同一个页面
# 2. 数据科学家的标准工具
# 3. 交互式探索数据

# Cell 类型:
# - Code: 执行 Python 代码
# - Markdown: 写文档(支持 LaTeX 数学公式)
# - Raw: 原始文本

# === 示例: 数据探索 ===

# In [1]:
import json
from collections import Counter

data = [
    {"name": "Alice", "dept": "Engineering", "level": 5},
    {"name": "Bob", "dept": "Engineering", "level": 3},
    {"name": "Charlie", "dept": "Marketing", "level": 4},
    {"name": "Diana", "dept": "Engineering", "level": 7},
    {"name": "Eve", "dept": "Marketing", "level": 2},
]

# In [2]:
dept_counts = Counter(item["dept"] for item in data)
print(dept_counts)
# Counter({'Engineering': 3, 'Marketing': 2})

# In [3]:
avg_level = {}
for dept, group in __import__('itertools').groupby(
    sorted(data, key=lambda x: x["dept"]),
    key=lambda x: x["dept"]
):
    levels = [item["level"] for item in group]
    avg_level[dept] = sum(levels) / len(levels)
print(avg_level)
# {'Engineering': 5.0, 'Marketing': 3.0}

核心差异

维度JShell / Kotlin REPLPython REPL / IPython / Jupyter
使用频率极少极频繁(Python 开发的核心工作方式)
第三方库支持困难原生支持
可视化Jupyter 支持 matplotlib, plotly 等
代码补全基础IPython 强大的 Tab 补全
调试有限IPython %debug, %pdb
分享不方便.ipynb 文件可直接分享
生态几乎没有庞大(数据科学标配)

常见陷阱

# 陷阱1: REPL 中定义的变量不会自动保存
# 退出 REPL 后所有变量丢失
# 解决: 用 %save 或 %store 保存到文件
# IPython: %save my_session.py 1-10
# IPython: %store data   # 跨 session 保存变量

# 陷阱2: Jupyter Notebook 的执行顺序
# Cell 可以乱序执行,导致状态混乱
# 解决: 定期 Kernel → Restart & Run All

# 陷阱3: Jupyter Notebook 不适合做版本控制
# .ipynb 是 JSON 格式,diff 不友好
# 解决: 用 jupytext(.py 和 .ipynb 双向同步)
# 或用 VS Code 的 Notebook 功能(原生 .py 文件中的 # %% 分隔)

# 陷阱4: REPL 中的 _ 变量
# _ 保存上一个表达式结果,如果你自己用 _ 做变量名会覆盖
# 但在模块/脚本中 _ 没有特殊含义

何时使用

场景工具
快速验证语法/APIPython REPL
性能测试、调试IPython(%timeit, %debug)
数据探索、可视化Jupyter Notebook
教学、演示Jupyter Notebook
日常开发VS Code 的交互式窗口(REPL 体验 + 编辑器)

1.6 IDE 选择: VS Code + Pylance vs PyCharm

Java/Kotlin 对比

JVM 生态的 IDE 选择几乎是确定的:
- Java: IntelliJ IDEA(社区版/旗舰版)
- Kotlin: IntelliJ IDEA(官方推荐)
- Android: Android Studio(基于 IntelliJ)

原因: Java/Kotlin 的类型系统复杂(泛型、继承、重载),需要深度语义分析。
IDE 的代码补全、重构、调试高度依赖类型信息。

Python 实现

Python 生态有两个主流选择,和 Java 生态的"一家独大"不同:

VS Code + Pylance(推荐)
// .vscode/settings.json — Python 项目推荐配置
{
    // Python 解释器选择
    "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",

    // Pylance 配置(语言服务器)
    "python.languageServer": "Pylance",
    "python.analysis.typeCheckingMode": "basic",
    // typeCheckingMode 选项:
    // "off"       — 不检查类型
    // "basic"     — 基本检查(推荐,不太严格)
    // "standard"  — 标准检查
    // "strict"    — 严格检查(接近 mypy --strict)

    "python.analysis.autoImportCompletions": true,
    "python.analysis.diagnosticSeverityOverrides": {
        "reportUnusedImport": "warning",
        "reportUnusedVariable": "warning",
        "reportMissingTypeStubs": "none"
    },

    // Ruff 集成(替代 flake8 + black + isort)
    "python.formatting.provider": "none",  // 禁用默认 formatter
    "[python]": {
        "editor.defaultFormatter": "charliermarsh.ruff",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.organizeImports": "explicit"  // Ruff 排序 imports
        }
    },

    // 测试
    "python.testing.pytestEnabled": true,
    "python.testing.pytestArgs": ["tests"],

    // 编辑器
    "editor.rulers": [88, 120],
    "editor.tabSize": 4,

    // 文件关联
    "files.associations": {
        "*.toml": "toml"
    }
}
// .vscode/extensions.json — 推荐扩展
{
    "recommendations": [
        "ms-python.python",           // Python 官方扩展
        "ms-python.vscode-pylance",   // 类型检查和智能补全
        "charliermarsh.ruff",         // Linter + Formatter
        "ms-python.debugpy",          // 调试器
        "ms-python.python-test-adapter",  // 测试集成
        "tamasfe.even-better-toml",   // TOML 语法支持
        "njpwerner.autodocstring",     // 自动生成 docstring
        "kevinrose.vsc-python-indent", // Python 缩进修正
    ]
}
PyCharm Professional
PyCharm 的优势:
1. 开箱即用,不需要配置扩展
2. 数据库工具、HTTP 客户端、Docker 支持内置
3. 科学模式(Jupyter 集成、matplotlib 预览)
4. Google App Engine、Django 模板支持

PyCharm 的劣势:
1. 重量级,内存占用大(类似 IntelliJ)
2. 专业版收费($249/年)
3. 插件生态不如 VS Code
4. 对非 Python 语言支持弱

推荐场景:
- 如果你习惯了 IntelliJ IDEA,PyCharm 的体验最接近
- 全栈开发(Python + DB + Docker)
- 公司付费

核心差异

维度IntelliJ IDEA (JVM)VS Code + Pylance (Python)PyCharm (Python)
定位全功能 IDE轻量编辑器 + 扩展全功能 IDE
启动速度慢(5-15s)快(1-3s)慢(5-15s)
内存占用1-4GB200-500MB1-3GB
插件生态丰富非常丰富有限
调试体验优秀良好(debugpy)优秀
重构强大基础良好
多语言Java/Kotlin 为主任何语言Python 为主
价格社区版免费免费社区版免费,专业版收费
AI 辅助JetBrains AIGitHub CopilotJetBrains AI

常见陷阱

// 陷阱1: VS Code 选择了错误的 Python 解释器
// 症状: import 报错,但 pip list 显示包已安装
// 解决: Cmd+Shift+P → Python: Select Interpreter → 选择 .venv 中的解释器

// 陷阱2: Pylance 报大量类型错误
// 原因: 第三方库没有类型存根(.pyi 文件)
// 解决: 安装 typeshed 或 types-xxx 包
// pip install types-requests types-boto3

// 陷阱3: PyCharm 社区版不支持很多功能
// 如: Jupyter、Docker、数据库工具、HTTP 客户端
// 这些在专业版才有

// 陷阱4: VS Code 的 Ruff 扩展和命令行 Ruff 版本不一致
// 解决: 在 settings.json 中指定 ruff 路径
// "ruff.path": ["${workspaceFolder}/.venv/bin/ruff"]

何时使用

场景推荐
多语言开发者VS Code(一个编辑器搞定所有语言)
从 IntelliJ 转过来的开发者PyCharm(体验最接近)
数据科学VS Code + Jupyter 扩展 或 JupyterLab
全栈 Python 开发PyCharm Professional
追求轻量和速度VS Code + Pylance + Ruff
团队标准化VS Code(.vscode/ 目录可提交到 git 共享配置)

1.7 Ruff: Rust 实现的超快 Linter+Formatter

Java/Kotlin 对比

<!-- Maven: 需要 3 个工具分别处理 lint、格式化、import 排序 -->
<plugins>
    <!-- Checkstyle: Lint -->
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>3.3.1</version>
    </plugin>
    <!-- SpotBugs: Bug 检测 -->
    <plugin>
        <groupId>com.github.spotbugs</groupId>
        <artifactId>spotbugs-maven-plugin</artifactId>
        <version>4.8.3</version>
    </plugin>
</plugins>
// Gradle: ktlint 处理 lint + 格式化
plugins {
    id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
}
// ktlint 是 Kotlin 生态的"一站式"方案,但速度远不如 Ruff

Python 实现

Ruff 是一个 Rust 实现的工具,替代了 flake8 + isort + black + pyupgrade 等多个工具,速度快 10-100 倍。

# 安装 Ruff
pip install ruff
# 或: brew install ruff
# 或: uv tool install ruff

# === Lint 检查 ===
ruff check .                    # 检查当前目录
ruff check src/ tests/          # 检查指定目录
ruff check --fix .              # 自动修复可修复的问题

# === 格式化 ===
ruff format .                   # 格式化当前目录
ruff format --check .           # 检查是否需要格式化(CI 用)

# === 排序 imports ===
# 已集成在 ruff check 中(I 规则)

# === 查看规则 ===
ruff rule --all                 # 列出所有规则
ruff rule --select E            # 列出 pycodestyle errors
ruff rule E501                  # 查看具体规则说明
# pyproject.toml 中的 Ruff 配置

[tool.ruff]
# 目标 Python 版本
target-version = "py310"
# 行长度限制
line-length = 120
# 源代码目录(用于 import 排序)
src = ["src"]
# 排除的目录
exclude = [
    ".git",
    ".venv",
    "__pycache__",
    "build",
    "dist",
    "*.egg-info",
]

# === Lint 规则选择 ===
[tool.ruff.lint]
# 启用的规则集
select = [
    "E",     # pycodestyle errors(基础代码风格)
    "W",     # pycodestyle warnings
    "F",     # pyflakes(未使用变量、重复 import 等)
    "I",     # isort(import 排序)
    "N",     # pep8-naming(命名规范)
    "UP",    # pyupgrade(自动升级到新语法)
    "B",     # flake8-bugbear(常见 bug 模式)
    "SIM",   # flake8-simplify(简化代码建议)
    "C4",    # flake8-comprehensions(推导式优化)
    "PTH",   # flake8-use-pathlib(用 pathlib 替代 os.path)
    "ERA",   # eradicate(移除注释掉的代码)
    "RUF",   # Ruff 特有规则
]
# 忽略的规则
ignore = [
    "E501",   # 行长度(交给 formatter)
    "B008",   # 函数调用作为默认参数(FastAPI 依赖注入需要)
]

# 每个文件的忽略
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]    # 测试文件允许 assert
"__init__.py" = ["F401"]  # __init__.py 允许未使用的 import

# isort 配置
[tool.ruff.lint.isort]
known-first-party = ["my_application"]
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]

# === Formatter 配置 ===
[tool.ruff.format]
quote-style = "double"          # 双引号
indent-style = "space"          # 空格缩进
docstring-code-format = true    # docstring 中的代码也格式化
line-ending = "auto"            # 自动检测换行符
# demo_ruff.py — Ruff 检查和修复示例

import os  # 未使用的 import → F401
import json
from pathlib import Path
from collections import OrderedDict  # 可以用 dict 替代 → C408

# 命名不符合规范 → N815
def BadFunctionName():
    pass

# 可简化 → SIM118
def get_value(d, key):
    if key in d.keys():    # SIM118: 用 "key in d" 替代 "key in d.keys()"
        return d[key]
    return None

# 使用了 os.path → PTH
config_path = os.path.join("config", "settings.json")  # PTH: 用 Path("config") / "settings.json"

# 注释掉的代码 → ERA
# old_function()
# deprecated_code()

# f-string 可以替代 → UP032
name = "World"
message = "Hello, {}".format(name)  # UP032: 用 f"Hello, {name}" 替代

# Mutable 默认参数 → B006(这是真正的 bug!)
def add_item(item, target=[]):  # B006: 不要用可变默认参数
    target.append(item)
    return target
# 运行 Ruff 检查
$ ruff check demo_ruff.py
demo_ruff.py:1:8: F401 [*] `os` imported but unused
demo_ruff.py:3:33: C408 Unnecessary `OrderedDict` call (rewrite as literal)
demo_ruff.py:7:5: N815 Function name `BadFunctionName` should not use CamelCase
demo_ruff.py:12:19: SIM118 Use `key in d` instead of `key in d.keys()`
demo_ruff.py:18:1: ERA001 Found commented-out code
demo_ruff.py:23:14: UP032 Use f-string instead of `format` call
demo_ruff.py:27:28: B006 Do not use mutable data structures for argument defaults

# 自动修复
$ ruff check --fix demo_ruff.py
# 修复了 F401(删除未使用 import)、UP032(转 f-string)、SIM118(简化)
# B006 和 C408 需要手动修复

# 格式化
$ ruff format demo_ruff.py

核心差异

维度Checkstyle + SpotBugs + ktlintRuff
实现语言JavaRust
速度慢(秒级)极快(毫秒级)
工具数量需要 2-3 个工具一个工具替代所有
规则兼容各自独立兼容 flake8 插件生态
配置文件XML / Kotlin DSLpyproject.toml
自动修复有限丰富(大部分规则可自动修复)
import 排序不涉及内置(isort 兼容)

常见陷阱

# 陷阱1: Ruff 和 flake8/black/isort 同时启用
# 会导致冲突和不一致
# 解决: 禁用其他工具,只用 Ruff

# 陷阱2: Ruff format 和 ruff check 的 line-length 不一致
# 确保 [tool.ruff] 和 [tool.ruff.format] 使用相同的 line-length

# 陷阱3: CI 中 Ruff 版本和本地不一致
# 解决: 在 CI 中锁定版本
# pip install ruff==0.4.4
# 或用 uv: uvx ruff@0.4.4 check .

# 陷阱4: 忽略了 B006(可变默认参数)—— 这是真正的 bug!
# 见 00-mindset-shift 的"常见陷阱"
def append_to(element, target=[]):  # BUG: 所有调用共享同一个 list!
    target.append(element)
    return target

# 正确:
def append_to(element, target=None):
    if target is None:
        target = []
    target.append(element)
    return target

何时使用

  • 所有 Python 项目: Ruff 已经是 2024+ 的事实标准,替代 flake8 + isort + black
  • CI/CD: Ruff 的速度优势在 CI 中尤其明显
  • pre-commit hook: Ruff 支持作为 pre-commit hook 使用
  • 不需要 Ruff 的情况: 团队已经稳定使用 flake8 + black 且没有痛点

1.8 mypy/pyright: 静态类型检查

Java/Kotlin 对比

// Java: javac 本身就是类型检查器
// 编译时就会报类型错误,不需要额外工具
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(42);        // 编译错误: incompatible types

// Java 的类型系统是编译器的一部分,不是可选的
public String greet(String name) {
    return "Hello, " + name;
}
// greet(42);            // 编译错误
// Kotlin: 同样,编译器就是类型检查器
// Kotlin 的类型推断更强大,但仍然是编译时检查
val names: MutableList<String> = mutableListOf()
names.add("Alice")
// names.add(42)         // 编译错误: Type mismatch

// Kotlin 的 null safety 也是编译时检查
fun greet(name: String): String {
    return "Hello, $name"
}
// greet(null)           // 编译错误: Null can not be a value of a non-null type

关键认知差异: Java/Kotlin 的类型检查是编译器内置的,不可关闭。Python 的类型检查是可选的——通过类型注解 + 外部工具(mypy/pyright)实现。Python 代码没有类型注解也能运行。

Python 实现

mypy — 最成熟的类型检查器
# 安装
pip install mypy

# 基础用法
mypy src/                    # 检查目录
mypy src/my_module.py        # 检查文件
mypy --strict src/           # 严格模式
# demo_type_check.py — 类型检查示例

from typing import Optional

# === 基础类型注解 ===
def greet(name: str) -> str:
    return f"Hello, {name}"

greet("Alice")     # OK
# greet(42)        # mypy error: Argument 1 has incompatible type "int"

# === Optional 类型(类似 Kotlin 的 ?) ===
def find_user(user_id: int) -> Optional[str]:
    """类似 Kotlin 的 fun findUser(userId: Int): String?"""
    if user_id == 1:
        return "Alice"
    return None  # Optional[str] 意味着可以是 str 或 None

result: str | None = find_user(1)  # Python 3.10+ 语法(替代 Optional[str])

# mypy 会强制你处理 None
if result is not None:
    print(result.upper())  # OK: mypy 知道这里 result 是 str

# === 集合类型 ===
from collections.abc import Sequence

def process_items(items: Sequence[int]) -> list[str]:
    """Sequence 是只读接口(类似 Kotlin 的 List,Java 的 Iterable)"""
    return [str(x) for x in items]

process_items([1, 2, 3])        # OK
process_items((1, 2, 3))        # OK (tuple 也是 Sequence)
# process_items(["a", "b"])     # mypy error: list[str] 不兼容 Sequence[int]

# === 字典类型 ===
def get_config() -> dict[str, int | str]:
    return {
        "timeout": 30,
        "host": "localhost",
        "port": 8080,
    }

config = get_config()
timeout: int = config["timeout"]   # mypy: dict 的值类型是 int | str,赋值给 int 不安全
# mypy error: Incompatible types (assignment has type "int | str", variable has type "int")

# === 类型别名 ===
type UserID = int  # Python 3.12+ 语法
# Python 3.10 用: UserID = int

def lookup(user_id: UserID) -> Optional[str]:
    return "Alice" if user_id == 1 else None

# === dataclass 类型检查 ===
from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int
    email: str | None = None  # 可选字段

user = User(name="Alice", age=30)  # OK: email 有默认值
# user = User(name=42, age=30)     # mypy error: name 应该是 str

# === Any vs Unknown ===
from typing import Any

def risky(x: Any) -> Any:
    # Any 会跳过所有类型检查——类似 Java 的 Object 或 Kotlin 的 Any
    return x + 1  # mypy 不报错,但运行时可能 TypeError

# 更安全的做法: 用 TypeGuard 或 assert
def safe_add(x: int | str) -> int | str:
    if isinstance(x, int):
        return x + 1
    return x  # mypy 知道这里 x 是 str
# 运行 mypy
$ mypy demo_type_check.py
demo_type_check.py:12: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
demo_type_check.py:35: error: Incompatible types in assignment (expression has type "int | str", variable has type "int")
Success: no errors found  # 修复后
pyright — VS Code Pylance 内置
# 安装(独立使用,不依赖 VS Code)
npm install -g pyright
# 或: pip install pyright

pyright src/
// VS Code 的 Pylance 就是 pyright
// 在 .vscode/settings.json 中配置
{
    "python.analysis.typeCheckingMode": "standard",
    // "off"      — 不检查
    // "basic"    — 基本检查(未定义变量、函数参数不匹配)
    // "standard" — 标准检查(推荐,接近 mypy 默认)
    // "strict"   — 严格检查(接近 mypy --strict)

    "python.analysis.diagnosticSeverityOverrides": {
        "reportMissingTypeStubs": "none",        // 不报缺少类型存根
        "reportUnusedImport": "warning",          // 未使用 import
        "reportUnusedVariable": "warning",        // 未使用变量
        "reportPrivateUsage": "warning",          // 访问私有成员
    }
}
严格模式配置
# pyproject.toml — mypy 严格模式配置
[tool.mypy]
python_version = "3.10"

# 严格模式(等价于 --strict 命令行参数)
strict = true
# strict 等价于同时启用以下所有选项:
# warn_return_any = true
# warn_unused_configs = true
# disallow_untyped_defs = true
# disallow_any_generics = true
# disallow_subclassing_any = true
# disallow_untyped_calls = true
# disallow_incomplete_defs = true
# check_untyped_defs = true
# disallow_untyped_decorators = true
# no_implicit_optional = true
# warn_redundant_casts = true
# warn_unused_ignores = true
# warn_no_return = true
# implicit_reexport = false
# strict_equality = true

# 检查范围
files = ["src", "tests"]

# 第三方库缺少类型存根时忽略
[[tool.mypy.overrides]]
module = [
    "httpx.*",
    "uvicorn.*",
    "celery.*",
]
ignore_missing_imports = true

# 测试文件可以宽松一些
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
# 严格模式下的代码示例
# strict_mode_demo.py

# 1. 所有函数必须有类型注解
def add(a: int, b: int) -> int:
    return a + b

# def bad_add(a, b):  # mypy --strict error: Function is missing type annotation
#     return a + b

# 2. 不能用 Any
# from typing import Any
# def process(data: Any) -> Any:  # error: Implicit "Any" in return type
#     pass

# 3. Optional 必须显式处理
def get_length(s: str | None) -> int:
    if s is None:
        return 0
    return len(s)

# def bad_get_length(s: str | None) -> int:
#     return len(s)  # error: Argument 1 has incompatible type "None"

# 4. 严格相等检查
def compare(x: int, y: int | str) -> bool:
    return x == y      # OK: int == (int | str) 是合法比较
    # return x is y    # error: Non-overlapping identity check (int and str)

# 5. 类型别名
type Config = dict[str, str | int | bool]

def load_config() -> Config:
    return {"host": "localhost", "port": 8080, "debug": True}

核心差异

维度javac / kotlincmypypyright
类型检查时机编译时(强制)运行前(可选)运行前(可选)
实现语言Java / KotlinPythonTypeScript
性能快(编译器一部分)中等
严格模式默认严格需配置 --strict需配置 "strict"
IDE 集成内置VS Code 扩展VS Code Pylance 内置
类型推断更强
协议支持接口ProtocolProtocol
渐进式采用不支持支持(逐文件/逐函数)支持

常见陷阱

# 陷阱1: 类型注解不影响运行时
def add(a: int, b: int) -> int:
    return a + b

add("hello", "world")  # mypy 报错,但 Python 运行时正常执行!返回 "helloworld"
# 类型注解只是"文档",不是"约束"——运行时 Python 完全忽略它们

# 陷阱2: 第三方库没有类型注解
# import some_old_library  # mypy: Library stubs not installed
# 解决1: 安装 types-xxx 包
# pip install types-requests
# 解决2: 在 pyproject.toml 中配置 ignore_missing_imports
# 解决3: 自己写 .pyi 存根文件

# 陷阱3: mypy 和 pyright 对同一代码给出不同结果
# 两个工具的实现不同,某些边缘情况行为不一致
# 建议: 选一个作为主力(推荐 pyright + VS Code),另一个作为 CI 补充

# 陷阱4: 过度依赖类型注解
# Python 的类型系统不如 Java/Kotlin 强大(没有重载、没有联合类型的穷尽检查)
# 不要试图用类型注解替代测试
# 类型注解是"锦上添花",不是"银弹"

# 陷阱5: 忘记处理 Optional
from typing import Optional

def get_name() -> Optional[str]:
    return None

name = get_name()
print(name.upper())  # mypy error: Item "None" of "Optional[str]" has no attribute "upper"
# 运行时: AttributeError: 'NoneType' object has no attribute 'upper'

# 正确:
if name is not None:
    print(name.upper())

何时使用

场景推荐
个人项目/脚本不需要(或只用 pyright basic 模式)
团队项目mypy standard 或 pyright standard
库开发(给别人用)mypy --strict(类型注解是库的 API 契约)
从 Java/Kotlin 转过来建议用严格模式——你习惯了强类型,类型注解能帮你减少错误
渐进式采用先加返回值类型,再加参数类型,最后启用严格模式

本章总结: 工具链速查表

需求推荐工具对标 JVM
Python 版本管理pyenvSDKMAN
虚拟环境uv / venv无直接对应(classpath 隔离)
包管理uv / poetryMaven / Gradle
项目配置pyproject.tomlpom.xml / build.gradle.kts
交互式开发IPython / JupyterJShell(极少用)
IDEVS Code + PylanceIntelliJ IDEA
Lint + FormatRuffCheckstyle + ktlint
类型检查mypy / pyrightjavac 内置

推荐的新项目初始化流程(2024+ 最佳实践):

# 1. 用 uv 初始化项目(最快)
uv init my-project
cd my-project

# 2. 添加依赖
uv add fastapi uvicorn pydantic
uv add --dev pytest ruff mypy

# 3. 编辑 pyproject.toml(添加 Ruff、mypy 配置,见 1.4 和 1.7 节)

# 4. 用 VS Code 打开
code .

# 5. 开始写代码
# uv run 会自动管理虚拟环境
uv run main.py
uv run pytest

核心认知: Python 的工具链是"组装式"的,不像 JVM 生态的"一站式"。这给了你更大的灵活性,但也需要你主动选择和配置。好消息是,2024+ 的 uv 正在把"组装"变成"开箱即用"。