从零搭建现代化 Python 开发环境
—— 自动控制仿真项目实战指南
从零开始,用最现代的工具链,搭建一个规范、可维护、可复现的 Python 项目。
本教程以「自动控制中的二阶动力学系统仿真」为实战案例,涵盖环境搭建、项目创建、版本管理、代码编写的完整流程。 同时覆盖 Windows 11 与 Ubuntu 24.04 两个系统,在有差异的地方会分别说明。
教程版本: v1.0
更新日期: 2026 年 7 月 25 日
作者: 镜上之窗
许可协议: MIT
写在前面
如果你正在学习 Python,或者准备开始一个科研/工程项目,想必会遇到这样的困惑:
pip install 装了一堆包,版本冲突了怎么办?
这些问题的本质,是缺少一套现代化、标准化的项目工作流。
Python 因其简洁易学而成为最受欢迎的编程语言,但"会写代码"和"会做项目"之间隔着一道鸿沟——项目结构怎么组织、依赖怎么管理、代码怎么版本控制、环境怎么复现……这些工程问题,教材上很少系统地教。
本教程就是为填补这道鸿沟而写的。
我们将从零开始,搭建一套完整的 Python 开发环境,并以自动控制中的二阶动力学系统仿真为例,走完从项目创建到代码运行的全过程。你不需要任何前置知识——跟着一步步操作,就能建立一套专业的开发习惯。
我们选择的工具链是:
- 🖥️ VS Code —— 目前最流行的代码编辑器,插件生态极其丰富
- ⚡ uv —— 用 Rust 编写的极速 Python 项目管理工具,替代 pip + venv + poetry 的组合
写给初学者的话:不必追求一次全部看懂。先跟着操作走一遍,有个整体印象,再回头细想每一步的意义。工程能力是在反复实践中建立的,本文只是你的起点。
目录
- 7.3 配置 justfile 与 just.py
第一章 基础工具安装
工欲善其事,必先利其器。 本章将搭建 Python 开发的三大基础工具:编辑器、包管理、版本控制。
1.1 VS Code 安装
Visual Studio Code(简称 VS Code)是微软开发的免费、开源代码编辑器。它本身只是一个"壳",真正的强大之处在于海量的插件生态——装什么插件,它就变成什么工具。
🪟 Windows 11
- 点击 Download for Windows 下载安装包
- 重要:在"选择附加任务"这一步,建议勾选以下选项:
- ✅ 添加到 PATH(允许从终端用
code 命令打开文件) - ✅ 将"通过 Code 打开"操作添加到 Windows 资源管理器目录上下文菜单
- ✅ 将"通过 Code 打开"操作添加到 Windows 资源管理器文件上下文菜单
安装完成后,在开始菜单中找到 VS Code 并打开。
💡 为什么要勾选"添加到 PATH"?PATH 是系统查找可执行程序的路径列表。把 VS Code 加入 PATH 后,你在任意目录的终端里输入 code 就能打开编辑器,输入 code . 就能用 VS Code 打开当前文件夹。这是开发者最常用的操作之一。
🐧 Ubuntu 24.04
方法一:通过官方 apt 仓库安装(推荐)
# 方式一:Snap 一键安装(推荐,最简单)sudo snap install code --classic# 方式二:通过 apt 仓库安装# 请搜索「VS Code Linux install」访问官方文档# 文档中会提供完整的 GPG 密钥和 apt 仓库配置命令
方法二:使用 Snap 安装(更简单,但启动稍慢)
sudo snap install code --classic
安装完成后,在终端输入 code 即可启动。
1.2 uv 安装与配置
uv 是什么?
uv 是 Astral 团队(也是 Ruff 代码检查工具的开发者)用 Rust 编写的 Python 包与项目管理工具。它可以替代传统的 pip + venv + poetry 组合,速度快了一个数量级,而且功能更完整:
| | |
|---|
| | uv pip install |
| | |
| | uv python install |
| | uv.lock |
| | uv_build |
💡 为什么选择 uv?
- ⚡ 极速:比 pip 快 10-100 倍(Rust 实现的依赖解析和下载)
- 🛠️ 一站式:不再需要 pip + venv + pyenv + poetry 的组合
- 🔒 可复现:自动生成
uv.lock 锁文件,确保团队环境一致 - 🎯 现代化:原生支持 PEP 517/518/621 等最新 Python 标准
安装 uv
🪟 Windows 11
按下 Win + X,选择"终端"或"PowerShell",执行:
# 方式一:使用 winget(推荐,最简单)winget install astral-sh.uv# 方式二:使用官方安装脚本# 请搜索「uv install」访问 uv 官方文档# 在 Install 章节找到 PowerShell 安装命令并复制执行
安装完成后,关闭当前终端,重新打开一个,输入以下命令验证:
uv --version
🐧 Ubuntu 24.04
打开终端,执行:
# 请搜索「uv install」访问 uv 官方文档# 在 Install 章节找到 Linux 安装命令并复制执行
安装脚本会自动将 uv 安装到 ~/.local/bin/,并写入 shell 配置文件。
安装完成后,执行以下命令让环境变量生效(或重新打开终端):
source ~/.bashrc # 如果你用 bash# 或者source ~/.zshrc # 如果你用 zsh
验证安装:
uv --version
💡 什么是"注册环境变量"?uv 安装完成后,需要让系统知道 uv 这个命令在哪里。安装脚本通常已经帮你做了这件事——它会在你的 shell 配置文件里添加一行路径配置。
但配置不会自动应用到已经打开的终端窗口,所以你需要关闭旧终端新开一个,或者手动 source 配置文件。这是初学者最容易踩的坑之一。
配置国内镜像源
uv 默认从国外的 PyPI 服务器下载包,在国内速度可能很慢。我们配置清华镜像源来加速。
uv 的全局配置文件位于:
- 🪟 Windows:
%APPDATA%\uv\uv.toml(通常是 C:\Users\你的用户名\AppData\Roaming\uv\uv.toml) - 🐧 Ubuntu:
~/.config/uv/uv.toml
🪟 Windows 11 配置方法
在 PowerShell 中执行:
# 创建配置目录(如果不存在)New-Item -ItemType Directory -Path $env:APPDATA\uv -Force# 创建并写入配置文件# 注意:下方的 url 和 python-install-mirror 需要替换为真实的镜像地址# 请搜索「清华大学 TUNA PyPI 镜像」获取@'[[index]]url = "替换为清华 PyPI 镜像地址"default = truepython-install-mirror = "替换为清华 python-build-standalone 镜像地址"'@ | Out-File -FilePath $env:APPDATA\uv\uv.toml -Encoding utf8
🐧 Ubuntu 24.04 配置方法
# 创建配置目录mkdir -p ~/.config/uv# 写入配置文件# 注意:下方的 url 和 python-install-mirror 需要替换为真实的镜像地址# 请搜索「清华大学 TUNA PyPI 镜像」获取cat > ~/.config/uv/uv.toml << 'EOF'[[index]]url = "替换为清华 PyPI 镜像地址"default = truepython-install-mirror = "替换为清华 python-build-standalone 镜像地址"EOF
💡 为什么要换源?PyPI(Python Package Index)是 Python 官方的包仓库,服务器在国外。国内网络环境下直接下载可能很慢甚至超时。
清华大学等机构在国内搭建了镜像站——定期同步官方仓库的内容,你从镜像站下载就和访问国内网站一样快。
default = true 表示将清华源设为默认源。python-install-mirror 则是针对 uv python install 命令的——uv 可以帮你下载管理多个 Python 版本,这个过程也需要加速。
配置完成后,可以用以下命令验证配置是否生效:
uv config list
1.3 Git 安装与基础配置
Git 是目前世界上最流行的分布式版本控制系统。简单来说,它帮你记录代码的每一次改动,随时可以回溯、对比、协作。
💡 为什么需要 Git?Git 能帮你:
- 💾 远程备份代码(推送到 GitHub / Gitee)
🪟 Windows 11
- Default editor:如果你还不熟悉 Vim,建议改成 "Use Visual Studio Code as Git's default editor"
- PATH environment:保持默认的 "Git from the command line and also from 3rd-party software"
- Line ending conversions:保持默认的 "Checkout Windows-style, commit Unix-style line endings"
💡 换行符选项是什么意思?Unix 风格的换行符(\n)是代码世界的通用语言。Windows 的 \r\n 在跨平台协作时会造成不必要的差异。选择此选项可以保证仓库中的代码始终使用 \n,而本地查看时 VS Code 会自动处理显示。
🐧 Ubuntu 24.04
Ubuntu 通常已经预装了 Git。如果没有:
sudo apt update && sudo apt install -y git
基础配置(双平台通用)
安装完 Git 后,第一件事是设置你的用户名和邮箱——每次提交代码时,Git 都会记录"是谁提交的"。
git config --global user.name "你的名字"git config --global user.email "你自己的邮箱"# 设置默认分支名为 main(而非旧默认的 master)git config --global init.defaultBranch main
💡 为什么要设置 init.defaultBranch?Git 的默认分支曾长期叫 master。2020 年后,GitHub 和社区逐步采用 main 作为默认分支名。提前设置可以保持与主流一致,避免首次推送时的困惑。
验证安装:
git --version
第二章 VS Code 插件配置
VS Code 本身只是一个文本编辑器,装上插件才会变成强大的 Python IDE。
打开 VS Code,点击左侧边栏的 扩展 图标(四个方块的图标),快捷键 Ctrl+Shift+X,在搜索框中输入插件名称,找到后点击 Install 即可。
下面分"必备"和"强烈推荐"两部分介绍。
2.1 必备插件(4 个)
| | |
|---|
| Chinese (Simplified) | | Chinese |
| Python | Microsoft 官方 Python 扩展,提供代码提示、调试、格式化等核心功能 | Python |
| Python Debugger | 专业 Python 调试器,提供断点调试、变量监控等高级功能 | Python Debugger |
| Pylance | 基于 Pyright 的快速语言服务器,提供精准的类型检查和智能提示 | Pylance |
💡 为什么需要这些插件?
- Python + Pylance:提供专业级的代码提示和类型检查,写代码时就能发现潜在错误
- Python Debugger:调试是开发的重要环节,
print() 调试法对复杂逻辑效率太低
2.2 强烈推荐插件(7 个)
这些插件不是必须的,但装上之后开发体验会提升一个档次。建议全部安装。
| |
|---|
| Ruff | 极速 Python 代码检查和格式化工具,比 flake8 + black + isort 更快 |
| Python Environments | |
| Even Better TOML | TOML 配置文件语法高亮和校验(编辑 pyproject.toml 必备) |
| Markdown All in One | Markdown 编辑增强:快捷键、目录生成、表格格式化 |
| Markdown Preview Enhanced | 增强版 Markdown 预览,支持数学公式、流程图、导出 PDF |
| Markdown Preview Mermaid Support | 在 Markdown 中渲染 Mermaid 流程图 |
| markdownlint | |
💡 小建议:可以在 VS Code 设置中开启 "Format On Save"(保存时自动格式化),这样每次按 Ctrl+S 保存,代码就自动变整齐了。
第三章 现代化项目结构:src 布局
在写代码之前,我们先聊聊"项目应该长什么样"。
3.1 什么是 src 布局
Python 项目的源代码组织方式主要有两种。
扁平布局(Flat Layout)——源码直接放在项目根目录:
my_project/├── pyproject.toml├── README.md└── my_project/ # 源码包直接在根目录 ├── __init__.py └── module.py
src 布局(Src Layout)——源码放在 src/ 子目录下:
my_project/├── pyproject.toml├── README.md└── src/ └── my_project/ # 源码包在 src/ 里面 ├── __init__.py └── module.py
本教程推荐并使用 src 布局。
3.2 为什么推荐 src 布局
初学者可能会问:多套一层 src/ 目录,不是更麻烦吗?
这是一个好问题。src 布局的好处,在项目规模变大后才会体现出来:
1. 强制测试"已安装版本"——这是最核心的原因
如果源码直接放在根目录,那么你在项目根目录下运行 Python 时,import my_project 导入的是当前目录下的源码,而不是虚拟环境中安装的包。
这意味着:你永远测试的是"源文件版本",而不是用户 pip install 之后拿到的"打包版本"。如果你的打包配置有问题(比如漏掉了某个数据文件、某个子模块没被包含进去),你在本地测试一切正常,用户安装后却用不了。
src 布局下,源码放在 src/ 里,项目根目录下没有同名包。import 只能导入虚拟环境里安装的版本。这就迫使你以"用户的视角"来测试,提前发现打包问题。
2. 避免导入歧义
项目根目录下经常会有一些临时脚本,比如 utils.py、test.py。如果你的项目里也有同名模块,扁平布局下就会产生冲突——Python 不知道你想导入哪一个。src 布局把源码"藏"在 src/ 里,根目录可以放心放各种脚本、配置文件。
3. 社区广泛认可
src 布局是 PyPA(Python 打包权威机构)推荐的标准做法,setuptools、hatchling、uv_build 等所有主流构建后端都原生支持。
💡 简单项目可以不用吗? 可以。如果你只是写几个脚本、做个小练习,扁平布局完全够用。但既然我们学习的是"现代化"的工作流,建议从一开始就养成好习惯。
3.3 三个容易混淆的名称
在 src 布局中,有三个不同的"名字"概念,初学者经常搞混:
control-sim/ # ① 外层文件夹名├── pyproject.toml│ └── name = "control-sim"# ② 项目名└── src/ └── control_sim/ # ③ 源码包名 └── __init__.py
| | | |
|---|
| ① 外层文件夹名 | | | |
| ② 项目名 | pyproject.toml | | PyPI 上的安装名,pip install control-sim |
| ③ 源码包名 | src/ | 不允许连字符 | Python import 时用的名字,import control_sim |
💡 为什么源码包名不允许连字符? Python 的 import 语句中,连字符 - 是减法运算符。import control-sim 会被 Python 解释为「导入 control,然后减去 sim」,这是一个语法错误。所以源码包名必须用下划线 _ 替代连字符。
关联机制:uv 的构建后端 uv_build 会自动把项目名规范化——转成小写,把连字符 - 和点 . 替换成下划线 _——作为默认的源码包名。所以 name = "control-sim" 自动对应 src/control_sim/。
第四章 使用 uv 创建项目
工具都装好了,现在正式创建我们的示例项目——一个自动控制仿真项目。
4.1 初始化项目
打开终端,进入你想存放项目的目录:
# 进入工作目录(根据你的实际情况调整)cd ~/projects # Linuxcd D:\projects # Windows
使用 uv 创建项目:
uv init --lib control-sim
参数说明:
--lib:创建库项目,使用 src 布局,带有构建系统配置
💡 uv init 的三种模式:
| | |
|---|
uv init my-project | | |
uv init --lib my-project | | |
uv init --package my-project | | |
执行完成后,uv 会自动创建 control-sim 文件夹,并生成基础的项目结构。
进入项目目录并用 VS Code 打开:
cd control-simcode .
注意末尾的 . 代表当前目录——这就是勾选"添加到 PATH"的好处。
4.2 项目结构解读
生成的项目目录长这样:
control-sim/├── .python-version # Python 版本锁定文件├── .venv/ # 虚拟环境目录(自动创建,不用管)├── pyproject.toml # 项目配置文件(核心)├── README.md # 项目说明文档├── uv.lock # 依赖锁定文件(自动生成)└── src/ └── control_sim/ ├── __init__.py └── py.typed
逐个说明:
- **
.python-version**:记录项目使用的 Python 版本号,uv 会根据这个文件自动选择对应的 Python 解释器。 .venv/:虚拟环境目录,所有安装的第三方包都在这里面。不要手动修改里面的内容,也不要提交到 Git。- **
pyproject.toml**:项目的核心配置文件,包含项目元数据、依赖列表、构建配置等。下面详细讲。 - **
README.md**:项目说明文档,用 Markdown 格式编写。别人打开你的项目,第一个看的就是这个。 uv.lock:依赖锁定文件,uv 自动生成,记录每个依赖的精确版本和哈希。应该提交到 Git,确保别人装出来的依赖和你完全一致。- **
src/control_sim/**:源代码目录,我们的代码都写在这里。 __init__.py:标识这个目录是一个 Python 包(可以为空文件)。py.typed:标识这是一个支持类型提示的包,类型检查工具会识别它。
4.3 pyproject.toml 详解
pyproject.toml 是现代化 Python 项目的统一配置文件,由 PEP 518 标准提出。在这之前,项目配置分散在 setup.py、setup.cfg、requirements.txt 等多个文件里,非常混乱。
打开 pyproject.toml,你会看到类似这样的内容:
[project]name = "control-sim"version = "0.1.0"description = "Add your description here"authors = [ { name = "Your Name", email = "你的邮箱" },]readme = "README.md"requires-python = ">=3.12"dependencies = [][build-system]requires = ["uv_build>=0.11.32,<0.12"]build-backend = "uv_build"
主要分为两部分:
[project] —— 项目元数据
version:版本号(遵循语义化版本规范:主版本.次版本.修订号)requires-python:要求的最低 Python 版本dependencies:运行时依赖列表,我们接下来会往里加东西
[build-system] —— 构建系统配置
这里使用的是 uv_build——uv 官方的构建后端,特点是零配置、速度快,自动识别 src 布局。纯 Python 项目用它就够了。
4.4 安装常用依赖
我们的控制仿真项目需要用到几个常用的科学计算库:
在项目根目录执行:
uv add numpy matplotlib tqdm
这条命令做了以下几件事:
- 将
numpy、matplotlib、tqdm 写入 pyproject.toml 的 dependencies
安装完成后,再执行一次:
uv sync
uv sync 会根据 pyproject.toml 和 uv.lock,确保虚拟环境中的依赖与锁定文件完全一致,同时以可编辑模式将项目本身安装到虚拟环境中。
💡 什么是可编辑模式?可编辑安装(editable install)会在虚拟环境中创建一个链接指向源码目录,修改 src/ 下的代码后无需重新安装即可生效。这对于开发过程非常方便。
验证一下安装是否成功:
uv run python -c "import numpy; import matplotlib; print('All packages imported successfully')"
如果没有报错,说明环境已经就绪。
💡 uv run 是什么?uv run 会自动激活当前项目的虚拟环境,然后执行后面的命令。你不需要手动 source .venv/bin/activate,直接 uv run python xxx.py 就行。这是 uv 非常方便的一点——永远不会因为"忘了激活虚拟环境"而装错包。
uv 工作流总结
创建项目 添加依赖 同步环境 运行代码uv init → uv add xxx → uv sync → uv run python ...
- **
uv add**:添加新依赖(写入配置 + 安装) - **
uv remove**:移除依赖(从配置删除 + 卸载) - **
uv sync**:根据配置同步环境(团队成员克隆项目后执行此命令即可还原环境) - **
uv run**:在项目的虚拟环境中运行命令(不需要手动激活 .venv)
第五章 Git 版本控制入门
代码写了就丢、改坏了回不去、多人协作一团糟——这些都是没有版本控制的痛点。Git 就是来解决这些问题的。
5.1 初始化仓库
在项目根目录执行:
git init
Git 会在当前目录创建一个 .git/ 隐藏文件夹,这就是 Git 仓库的数据库。所有的版本历史都存在这里。
5.2 .gitignore 文件
不是所有文件都需要提交到版本控制。虚拟环境、缓存文件、编辑器配置等,每台机器都不一样,提交了反而添麻烦。
在项目根目录创建一个名为 .gitignore 的文件(注意开头的点),写入以下内容:
# ===== Python =====__pycache__/*.py[cod]*$py.class*.so.Pythonbuild/dist/*.egg-info/*.egg# ===== 虚拟环境 =====.venv/venv/env/# ===== 工具缓存 =====.pytest_cache/.mypy_cache/.ruff_cache/.ipynb_checkpoints/htmlcov/.tox/.nox/# ===== 编辑器 =====.vscode/.idea/*.swp*.swo# ===== 系统文件 =====.DS_StoreThumbs.db
💡 为什么要有 .gitignore?Git 的设计哲学是"只跟踪源代码"——也就是人写的、有意义的文件。
虚拟环境 .venv/ 是自动生成的,任何人拿到代码执行 uv sync 就能重建出来,不需要也不应该放进 Git。同理,__pycache__/ 是 Python 自动生成的字节码缓存,每次运行都会变,提交了没有意义。
.gitignore 告诉 Git:"这些文件你忽略就行,不用管。"
5.3 Git 核心概念:工作区、暂存区、仓库
在开始日常使用之前,理解 Git 的三个核心区域非常重要:
工作区(Working Directory) → 暂存区(Staging Area) → 本地仓库(Repository) 你编辑的文件 git add 后的文件 git commit 后的快照
💡 什么是暂存区?暂存区(Staging Area)是 Git 的一个核心概念。你可以把它理解为"提交前的准备区"。
不是所有修改都要一次性提交。比如你同时改了 A 功能和 B 功能,但想分成两次提交(方便后续追溯),就可以先暂存 A 相关的文件,提交一次;再暂存 B 相关的文件,再提交一次。
初学者可能觉得多此一举,但这是 Git 精细管理变更的基础。用多了就会 appreciate 这个设计。
5.4 在 VS Code 中使用 Git
VS Code 左侧边栏有一个 源代码管理 图标(分支形状,快捷键 Ctrl+Shift+G)。点击它就能看到 Git 面板。
基本操作流程是:
- 暂存 → 点击文件旁边的
+ 号,把文件加入暂存区
5.5 提交信息规范
好的提交信息应该简洁明了地说明"这次提交做了什么"。推荐遵循 Conventional Commits 规范:
<类型>: <简要描述>
常用类型:
| | |
|---|
feat | | feat: 添加二阶系统仿真模块 |
fix | | fix: 修复 matplotlib 中文显示问题 |
docs | | docs: 更新 README 安装说明 |
style | | style: 统一代码缩进风格 |
refactor | | refactor: 提取公共绘图函数 |
test | | test: 添加阶跃响应单元测试 |
chore | | chore: 更新依赖版本 |
现在我们来做项目的第一次提交:
# 暂存所有文件git add .# 查看即将提交的文件(可选,用于确认)git status# 首次提交git commit -m "feat: 初始化项目结构与依赖配置"
提交完成后,你的第一个版本就被永久记录下来了。以后任何时候想回到这个状态,都可以通过 Git 找回。
💡 良好的 Git 实践建议:
- 提交消息清晰明确,描述"做了什么"而非"怎么做的"
- 定期推送到远程仓库(如 GitHub / Gitee)
第六章 实战:二阶系统阶跃响应
环境搭好了,版本控制也建好了,现在写点真东西。
我们来实现一个经典的自动控制案例——二阶动力学系统的单位阶跃响应,并用 matplotlib 画出曲线。
6.1 理论背景
二阶系统是控制理论中最基础、最重要的系统模型之一。许多实际系统(如弹簧-阻尼器、电机、飞行器姿态控制)都可以近似为二阶系统。
它的传递函数标准形式为:
其中:
- (omega_n):自然频率(系统固有的振荡频率,单位 rad/s)
- (zeta):阻尼比(无量纲,决定系统的振荡特性)
📖 延伸阅读:可搜索「二阶系统」「传递函数」「控制理论」等关键词深入了解。
不同的阻尼比决定了系统的响应特性:
我们将使用解析解来计算不同阻尼比下的阶跃响应,并绘制对比曲线。
6.2 编写仿真代码
在 src/control_sim/ 目录下创建两个文件:
文件一:second_order.py —— 仿真核心逻辑
"""二阶动力学系统仿真模块。实现标准二阶系统的单位阶跃响应计算。传递函数: G(s) = ω_n² / (s² + 2ζω_n s + ω_n²)"""from __future__ import annotationsimport numpy as npdefstep_response( zeta: float, omega_n: float, t_end: float = 10.0, num_points: int = 1000,) -> tuple[np.ndarray, np.ndarray]:"""计算二阶系统的单位阶跃响应。 根据阻尼比的不同,使用对应的解析解公式。 Args: zeta: 阻尼比 (无量纲) omega_n: 自然频率 (rad/s) t_end: 仿真结束时间 (秒) num_points: 时间采样点数 Returns: time: 时间数组 output: 系统输出数组(无量纲) """ time = np.linspace(0, t_end, num_points)if zeta < 1.0:# 欠阻尼:振荡衰减 omega_d = omega_n * np.sqrt(1 - zeta**2) # 阻尼自然频率 phi = np.arccos(zeta) # 相位角 output = 1.0 - (np.exp(-zeta * omega_n * time) / np.sqrt(1 - zeta**2)) \ * np.sin(omega_d * time + phi)elif abs(zeta - 1.0) < 1e-9:# 临界阻尼:快速无振荡 output = 1.0 - np.exp(-omega_n * time) * (1.0 + omega_n * time)else:# 过阻尼:缓慢无振荡 sqrt_term = np.sqrt(zeta**2 - 1.0) pole1 = -zeta * omega_n + omega_n * sqrt_term pole2 = -zeta * omega_n - omega_n * sqrt_term A = omega_n**2 / (pole1 * pole2) B = omega_n**2 / (pole1 * (pole1 - pole2)) C = omega_n**2 / (pole2 * (pole2 - pole1)) output = A + B * np.exp(pole1 * time) + C * np.exp(pole2 * time)return time, outputdefcompute_settling_time( t: np.ndarray, y: np.ndarray, threshold: float = 0.02,) -> float | None:"""计算调节时间。 调节时间定义为:系统响应进入并保持在稳态值 ±threshold 范围内的时间。 这是评价控制系统性能的重要指标之一。 Args: t: 时间数组 y: 响应数组 threshold: 稳态误差带(默认 2%,即 ±2%) Returns: 调节时间(秒),如果从未进入误差带则返回 None """ steady_state = y[-1] lower = steady_state * (1 - threshold) upper = steady_state * (1 + threshold)# 从后向前搜索,找到最后一个超出误差带的时刻for i in range(len(t) - 1, -1, -1):if y[i] < lower or y[i] > upper:if i < len(t) - 1:return t[i + 1]returnNonereturn t[0]
💡 **关于 compute_settling_time**:调节时间(settling time)是控制系统中衡量响应速度的重要指标。它表示系统输出进入并保持在稳态值 ±2% 范围内所需的时间。在工程实践中,这个指标比"到达稳态"更有意义,因为我们需要知道系统"稳定下来"的确切时刻。
文件二:plot_step.py —— 绘图入口
"""绘制不同阻尼比下的二阶系统阶跃响应曲线。"""from __future__ import annotationsimport matplotlib.pyplot as pltimport numpy as npfrom matplotlib.font_manager import FontProperties, findfontfrom control_sim.second_order import compute_settling_time, step_responsedef_configure_chinese_font() -> str:"""自动检测并配置 matplotlib 中文字体。 按优先级依次尝试系统中常见的中文字体,找到第一个可用的即配置并返回。 如果没有找到任何中文字体,返回空字符串并给出提示。 Returns: 使用的字体名称,未找到则返回空字符串 """ candidates = ["Microsoft YaHei", # Windows 11 默认"SimHei", # Windows 经典黑体"WenQuanYi Micro Hei", # Linux 文泉驿微米黑"Noto Sans CJK SC", # Linux Noto 字体 ]for name in candidates:try:# 如果 findfont 返回的不是默认字体,说明找到了if findfont(FontProperties(family=name)) != findfont(FontProperties()): plt.rcParams["font.sans-serif"] = [name] + plt.rcParams["font.sans-serif"] plt.rcParams["axes.unicode_minus"] = Falsereturn nameexcept Exception:continue# 未找到中文字体,给出提示 plt.rcParams["axes.unicode_minus"] = False print("⚠️ 未找到中文字体,图表中的中文可能显示为方块。") print(" Windows 用户通常无需担心;Linux 用户可执行:sudo apt install fonts-wqy-microhei")return""defmain() -> None:"""主函数:计算并绘制阶跃响应曲线。"""# ===== 配置中文字体 ===== font_name = _configure_chinese_font()if font_name: print(f"✓ 使用中文字体: {font_name}")# ===== 参数设置 ===== omega_n = 2.0 * np.pi # 自然频率:2π rad/s zeta_list = [0.2, 0.5, 0.707, 1.0, 1.5] # 不同阻尼比 t_end = 5.0# 仿真时长# ===== 创建画布 ===== fig, ax = plt.subplots(figsize=(12, 7), dpi=120)# ===== 逐个计算并绘图 =====for zeta in zeta_list: t, y = step_response(zeta, omega_n, t_end=t_end)# 构造图例标签 label = f"ζ = {zeta}"if zeta < 1.0: label += "(欠阻尼)"elif abs(zeta - 1.0) < 1e-9: label += "(临界阻尼)"else: label += "(过阻尼)" ax.plot(t, y, linewidth=2, label=label)# 标注调节时间(仅对有稳态值的情况)if zeta > 0: ts = compute_settling_time(t, y, threshold=0.02)if ts isnotNoneand ts < t_end: ax.axvline(x=ts, color=ax.lines[-1].get_color(), linestyle="--", alpha=0.3) ax.annotate(f"ts={ts:.1f}s", xy=(ts, 1.0), xytext=(ts + 0.15, 0.55 + zeta * 0.15), fontsize=8, color=ax.lines[-1].get_color(), arrowprops=dict(arrowstyle="->", alpha=0.4), )# ===== 图形装饰 ===== ax.axhline(y=1.0, color="gray", linestyle=":", linewidth=1, alpha=0.5) ax.text(t_end * 0.93, 1.02, "稳态值", fontsize=9, color="gray", ha="center") ax.set_title(f"二阶系统单位阶跃响应(ωₙ = {omega_n:.1f} rad/s)", fontsize=16, pad=15) ax.set_xlabel("时间 t (秒)", fontsize=13) ax.set_ylabel("输出 y(t)", fontsize=13) ax.grid(True, linestyle="--", alpha=0.6) ax.legend(fontsize=11, loc="right") ax.set_xlim(0, t_end) ax.set_ylim(bottom=-0.1) ax.tick_params(labelsize=11) fig.tight_layout()# ===== 保存并显示 ===== output_path = "step_response.png" fig.savefig(output_path, dpi=150, bbox_inches="tight") print(f"✓ 图片已保存为 {output_path}") plt.show()if __name__ == "__main__": main()
💡 代码亮点说明:
- 类型注解:使用
float、np.ndarray 等类型注解,提高代码可读性和 IDE 支持 - 文档字符串:每个函数都有清晰的 docstring,说明参数和返回值
- 跨平台中文字体检测:
_configure_chinese_font() 自动检测操作系统并选择合适的字体,Windows 用户和 Linux 用户无需额外配置 - 调节时间标注:自动计算并标注每条曲线的调节时间,直观展示系统性能差异
- **
from __future__ import annotations**:启用延迟求解类型注解,支持 float | None 等现代语法
6.3 运行与验证
在项目根目录执行:
uv run python -m control_sim.plot_step
💡 -m 是什么意思?-m 表示以模块方式运行。python -m control_sim.plot_step 等价于"找到 control_sim.plot_step 这个模块,执行它的 __main__ 部分"。
为什么不直接 python src/control_sim/plot_step.py?因为直接运行脚本文件时,Python 的模块搜索路径可能不对,导致 import control_sim.second_order 失败。用 -m 方式运行是更规范的做法。
如果一切正常,你会看到:
- 弹出一个图形窗口,显示五条不同阻尼比的阶跃响应曲线
- 项目根目录下生成
step_response.png 图片
观察曲线,你可以直观地看到:
这就是控制理论中"响应速度"与"稳定性"的经典权衡。
现在提交我们的仿真代码:
git add .git commit -m "feat: 添加二阶系统阶跃响应仿真与可视化"
第七章 效率提升:just 任务运行器
项目开发中,经常有一些重复性的命令操作:清理缓存、运行测试、启动服务……每次都敲一长串命令很麻烦。
just 是一个用 Rust 编写的命令运行器(类似 Make,但更简单、更现代)。你可以把常用命令写在 justfile 里,然后用 just 命令名 来执行。
7.1 just 是什么
简单理解:just 就是一个"项目命令快捷方式管理器"。
比如你定义了:
clean: uv run python just.py clean
以后只需要输入 just clean,就会自动执行后面那串命令。
💡 什么时候需要清理缓存?最常见的场景是使用 Numba 进行 JIT 加速时。Numba 会把编译后的机器码缓存起来,如果缓存损坏或版本不兼容,可能导致奇怪的错误——比如修改了代码但行为没有变化。这时候执行 just clean 清理所有缓存,就能确保下次运行时重新编译。
7.2 安装 just
推荐通过 uv 的工具管理功能安装:
uv tool install rust-just
💡 uv tool install 是什么? 它会将工具安装到一个独立的隔离环境中,然后在全局可用。这类似于 pipx,但由 uv 管理,速度更快。安装后,just 命令可以在任何目录下直接使用,无需激活虚拟环境。
安装完成后验证:
just --version
💡 为什么用 uv tool install 而不是系统包管理器?
- 跨平台一致:Windows 和 Linux 都是同一条命令安装
- 隔离环境:每个工具都有自己的独立环境,不会污染系统 Python
- 统一管理:
uv tool list 查看所有工具,uv tool upgrade 一键升级
7.3 配置 justfile 与 just.py
以下配置来自作者长期的 Python 开发实践,已在多个项目中验证。
第一步:创建 justfile
在项目根目录创建名为 justfile 的文件(没有后缀名),写入:
# 默认显示帮助default: list# 清理项目缓存文件clean: uv run python just.py clean# 查看当前环境信息info: uv run python just.py info# 显示可用命令列表list: uv run python just.py list
⚠️ 注意:justfile 中缩进必须使用 Tab(制表符),不能用空格。Windows 下创建文件时注意不要变成 justfile.txt。
第二步:创建 just.py
将以下代码保存到项目根目录(和 pyproject.toml 同级):
"""项目管理辅助工具提供缓存清理、环境信息查询、帮助文档等功能。"""from __future__ import annotationsimport platformimport shutilimport sysfrom collections.abc import Callablefrom dataclasses import dataclass, fieldfrom pathlib import Path# ==================== 配置定义 ====================@dataclassclassCleanConfig:"""缓存清理的配置项。""" max_display: int = 10 venv_dir: str = ".venv" cache_patterns: set[str] = field( default_factory=lambda: {"__pycache__",".pytest_cache",".mypy_cache",".ruff_cache",".ipynb_checkpoints","htmlcov",".tox",".nox", } )CLEAN_CONFIG = CleanConfig()# ==================== 核心业务逻辑 ====================defscan_cache_dirs(root: Path, config: CleanConfig) -> list[Path]:"""扫描给定根目录下的所有缓存目录。""" targets = [] patterns = config.cache_patterns venv_dir = config.venv_dirfor pattern in patterns:for p in root.rglob(pattern):# 跳过虚拟环境目录中的缓存if any(part.casefold() == venv_dir.casefold() for part in p.parts):continue targets.append(p)return targetsdefremove_paths(paths: list[Path]) -> int:"""批量删除路径,并返回成功删除的数量。""" success = 0for p in paths:try:if p.is_dir(): shutil.rmtree(p)else: p.unlink() success += 1except Exception as e: # noqa: BLE001 print(f" ⚠️ 无法删除 {p.name}: {e}")return success# ==================== 功能函数 ====================defclean() -> None:"""清理项目缓存""" root = Path.cwd() targets = scan_cache_dirs(root, CLEAN_CONFIG)ifnot targets: print("✅ 未发现缓存目录,环境已是干净的。")return max_display = CLEAN_CONFIG.max_display targets.sort(key=lambda x: len(x.parts), reverse=True) print(f"📁 发现 {len(targets)} 个缓存目录:")for p in targets[:max_display]: print(f" 🗑️ {p.relative_to(root)}")if len(targets) > max_display: print(f" ... 还有 {len(targets) - max_display} 个未显示") print("\n正在清理...") success_count = remove_paths(targets) print(f"✅ 成功清理 {success_count} 个目录。")definfo() -> None:"""显示环境信息""" print("🖥️ 环境信息:") print(f" 🐍 Python : {platform.python_version()} ({sys.executable})") print(f" 📂 工作目录 : {Path.cwd()}") print(f" 💻 操作系统 : {platform.system()}{platform.release()}") print(f" 🏷️ 主机名 : {platform.node()}")defhelp_menu() -> None:"""显示帮助菜单""" print("📋 可用命令:\n") max_len = max(len(k) for k in COMMANDS)for name, func in COMMANDS.items(): desc = "无描述"if func.__doc__: desc = func.__doc__.strip().split("\n")[0] print(f" {name.ljust(max_len)} : {desc}")# ==================== 路由注册 ====================COMMANDS: dict[str, Callable] = {"clean": clean,"info": info,"list": help_menu,}# ==================== 程序入口 ====================defmain() -> None:"""程序入口""" args = sys.argv[1:] cmd_name = args[0] if args else"list"if cmd_name in COMMANDS: COMMANDS[cmd_name]()else: print(f"❌ 未知命令: {cmd_name}\n") help_menu() sys.exit(1)if __name__ == "__main__": main()
7.4 使用 just
在项目根目录下,直接输入:
# 显示所有可用命令just# 清理缓存just clean# 查看环境信息just info
示例输出——just clean:
📁 发现 15 个缓存目录: 🗑️ src/control_sim/__pycache__ 🗑️ tests/__pycache__ 🗑️ .ruff_cache ... 还有 12 个未显示正在清理...✅ 成功清理 15 个目录。
示例输出——just info:
🖥️ 环境信息: 🐍 Python : 3.12.0 (/path/to/.venv/bin/python) 📂 工作目录 : /path/to/control-sim 💻 操作系统 : Linux 6.5.0-28-generic 🏷️ 主机名 : your-hostname
现在提交我们的工具配置:
git add .git commit -m "chore: 添加 just 任务运行器配置"
附录
A. 常见问题(FAQ)
Q1:uv 命令找不到怎么办?
这是环境变量没有生效的问题。
- 🪟 Windows:关闭所有终端窗口,重新打开一个。如果还不行,检查用户环境变量的 Path 中是否包含
%USERPROFILE%\.local\bin。 - 🐧 Ubuntu:执行
source ~/.bashrc(或对应 shell 的配置文件)。如果是通过桌面图标打开的 VS Code 终端,可能需要注销重登。
Q2:import control_sim 报错 ModuleNotFoundError?
常见原因有三个:
- **没有用
uv run**:直接 python xxx.py 用的是系统 Python,不是项目的虚拟环境。加上 uv run 前缀。 - 项目没有安装到虚拟环境:执行
uv sync,确保项目以可编辑模式安装。 - 包名写错了:import 用的是源码包名(带下划线的
control_sim),不是项目名 control-sim。
Q3:matplotlib 中文还是方块?
- 确认系统安装了中文字体(Linux 需要手动装:
sudo apt install fonts-wqy-microhei) - 确认
_configure_chinese_font() 函数正常执行(查看终端输出) - 清除 matplotlib 字体缓存:删除
~/.cache/matplotlib/ 目录(Linux)或 %USERPROFILE%\.matplotlib\(Windows),然后重新运行
Q4:虚拟环境在哪?我怎么激活它?
uv 创建的虚拟环境默认在项目根目录的 .venv/ 文件夹里。
你其实不需要手动激活——uv run 会自动用它。如果确实需要手动激活:
# Windows PowerShell.venv\Scripts\Activate.ps1# Windows Git Bashsource .venv/Scripts/activate# Ubuntusource .venv/bin/activate
激活后终端提示符前面会出现 (.venv) 标识。退出用 deactivate。
Q5:uv.lock 文件要提交到 Git 吗?
要提交。
uv.lock 记录了所有依赖的精确版本号和哈希值。别人拿到你的项目,执行 uv sync 就能装出和你完全一样的依赖环境,避免"我这能跑你那跑不了"的尴尬。
Q6:.venv 目录好大,可以删吗?
可以删,不会丢代码。删了之后执行 uv sync 会根据 uv.lock 完整重建出来。
Q7:uv sync 很慢怎么办?
确认已配置国内镜像源(见 1.2 节)。配置后下载速度会显著提升。
B. 常用命令速查表
uv 命令
| |
|---|
uv init --lib 项目名 | |
uv init --package 项目名 | |
uv add 包名 | |
uv add --dev 包名 | |
uv remove 包名 | |
uv sync | |
uv run python 文件.py | |
uv run python -m 模块名 | |
uv pip list | |
uv python install 3.12 | |
uv tool install 工具名 | |
Git 命令
| |
|---|
git init | |
git status | |
git add . | |
git add <文件> | |
git commit -m "消息" | |
git log --oneline | |
git diff | |
git diff --staged | |
git restore <文件> | |
git restore --staged <文件> | |
git branch 分支名 | |
git checkout 分支名 | |
git merge 分支名 | |
just 命令
C. 完整项目结构参考
完成本教程后,你的项目目录应该如下所示:
control-sim/├── .python-version # Python 版本锁定(uv 自动生成)├── .gitignore # Git 忽略规则├── pyproject.toml # 项目配置(元数据、依赖、构建系统)├── uv.lock # 依赖锁文件(uv 自动生成)├── README.md # 项目说明├── justfile # just 任务管理配置├── just.py # just 任务管理脚本├── step_response.png # 生成的阶跃响应图│├── src/ # 源代码目录│ └── control_sim/ # 源码包名(import control_sim)│ ├── __init__.py # 包标识│ ├── py.typed # 类型标记(uv 自动生成)│ ├── second_order.py # 二阶系统仿真模块│ └── plot_step.py # 绘图入口│└── .venv/ # 虚拟环境(自动生成,不追踪)
关键文件说明
| | |
|---|
pyproject.toml | | |
uv.lock | | |
.python-version | | |
.gitignore | | |
just.py | | |
justfile | | |
.venv/ | | |
__pycache__/ | | |
D. 进阶学习建议与资源
恭喜你完成了这篇教程!回顾一下,你已经学会了:
- ✅ 安装并配置 VS Code、uv、Git 三大基础工具
- ✅ 用 Git 做版本控制,在 VS Code 中进行提交
- ✅ 编写并运行一个二阶系统仿真程序,绘制中文标注的曲线图
这是一套完整的、现代化的 Python 项目工作流。掌握了它,你以后新建任何 Python 项目都有了标准的起点。
推荐学习路径
阶段一:巩固基础(1-2 周)
- [ ] 熟悉 Python 类型系统(
typing 模块)
阶段二:工程化实践(1-2 个月)
- [ ] 学习持续集成(CI/CD,如 GitHub Actions)
- [ ] 编写完整的 API 文档(使用 Sphinx 或 MkDocs)
- [ ] 探索性能优化技巧(如 Numba、Cython)
阶段三:专业级项目(3-6 个月)
推荐资源
官方文档
书籍推荐
- 《流畅的 Python(第 2 版)》—— Luciano Ramalho
- 《Python 编程:从入门到实践》—— Eric Matthes
- 《Effective Python》—— Brett Slatkin
在线资源
- Real Python —— 高质量 Python 教程
写在最后
编程是一门实践的艺术,最好的学习方式就是动手做。工具会不断进化,但好的习惯是持久的:使用虚拟环境隔离依赖、用锁文件确保可复现、用版本控制追踪变更、用项目结构表达意图。
一步一步来,工程能力是在实践中积累的。祝编码愉快!🎉