← 返回学习中心ARC LAB / LEARNING NOTES

Python 专区

Python 项目组织与工程规范

把脚本整理成可安装、可测试、可交接的命令行项目。

展开本篇目录

一、为什么要把脚本整理成项目?

一个脚本在你电脑上运行,并不代表同伴能找到入口、安装依赖或理解输出。项目组织的目标是让输入、处理逻辑、配置、结果和验证方式都有明确位置。

本篇先修知识为 Python 函数、模块与基本终端操作。学完应能将一个小型成绩统计程序拆成包,通过命令行运行,并由测试验证核心逻辑。项目越小,结构越应克制,不需要一开始就引入复杂框架。

项目从输入经过命令行入口、业务逻辑和校验生成输出,测试独立验证业务逻辑

二、先区分解释器、包、模块和工作目录

名称 含义 例子
解释器 真正执行代码的 Python .venv/bin/python
模块 可导入的 Python 文件 stats.py
包 组织模块的命名空间 scorelab
工作目录 相对路径解析的起点 终端当前目录
项目根目录 配置、源码和文档所在目录 pyproject.toml 所在处

python -m pip 让 pip 与当前解释器配套。不要在项目中命名 json.py、sqlite3.py 等文件,否则可能遮蔽标准库。

python -m venv .venv

Windows PowerShell:.venv\Scripts\Activate.ps1;macOS/Linux:source .venv/bin/activate。如果不想激活,也可直接调用虚拟环境内的 Python。

三、从最小可维护结构开始

scorelab-project/
├── pyproject.toml
├── README.md
├── .gitignore
├── src/
│ └── scorelab/
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli.py
│ └── stats.py
└── tests/
└── test_stats.py

src 布局将源码与项目根目录隔开,有助于避免“在根目录碰巧能导入,安装后却失败”。它需要安装项目,而不是到处追加 sys.path。小型一次性脚本也可以使用平铺结构,关键是统一入口和导入方式。

四、pyproject.toml:记录项目事实

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "arclab-scorelab"
version = "0.1.0"
description = "A small score summary exercise"
requires-python = ">=3.10"
dependencies = []

[project.scripts]
scorelab = "scorelab.cli:main"

[tool.setuptools.packages.find]
where = ["src"]

项目分发名 arclab-scorelab 与导入名 scorelab 可以不同。build-system 声明构建工具;dependencies 声明运行依赖;开发工具依赖应另外管理。这个练习不需要上传到 PyPI。

在根目录运行 python -m pip install -e .,可编辑安装会让源码修改在环境中生效。安装可能需要联网获取构建工具,但实际示例运行只用标准库。

五、核心函数:与终端和文件操作分离

src/scorelab/stats.py:

import math

def average(values: list[float]) -> float:
if not values:
raise ValueError("at least one score is required")
if any(not math.isfinite(x) or not 0 <= x <= 100 for x in values):
raise ValueError("scores must be finite and between 0 and 100")
return sum(values) / len(values)

函数接受数值列表,返回一个数,不直接读取 CSV、不打印日志,也不退出进程,因此容易单独测试。类型注解帮助读者和检查工具理解约定,不会自动执行运行时类型校验;上例假设调用方已经把输入转换为数值。

六、命令行入口与可预期的错误

src/scorelab/cli.py:

import argparse
from .stats import average

def main():
parser = argparse.ArgumentParser(description="Compute a mean score")
parser.add_argument("scores", nargs="+", type=float)
args = parser.parse_args()
try:
result = average(args.scores)
except ValueError as exc:
parser.error(str(exc))
print(f"{result:.2f}")

src/scorelab/__main__.py:

from .cli import main
main()

src/scorelab/__init__.py 可以暂时为空。

python -m scorelab 80 90
scorelab 80 90

两条命令都输出 85.00。python -m scorelab 120 会打印错误并以非零状态退出。不要捕获全部异常后仍打印“成功”,这会让自动化脚本把失败当成成功。

七、路径、配置与日志

7.1 路径由入口决定

from pathlib import Path
input_path = Path("data/raw_scores.csv")
print(input_path.resolve())

相对路径依赖当前工作目录。更通用的入口是 --input 和 --output,由用户明确提供;创建输出目录时使用 mkdir(parents=True, exist_ok=True)。不要为方便而在函数内部全局 chdir。

7.2 配置和密钥分开

普通配置可放 TOML/JSON 或命令行参数。Token 不写进源码、Notebook 输出或截图;从环境变量读取,并提交不含真实值的 .env.example。仅把文件加入 .gitignore 不能删除已经提交进历史的秘密。

7.3 日志保留运行上下文

import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logging.info("loaded %d records", 8)

记录输入路径、数量、规则和错误,不输出个人敏感数据或密钥。核心库代码使用自己的 logger,通常由命令行入口配置全局日志。

八、测试:让规则成为可执行的说明

tests/test_stats.py:

import unittest
from scorelab.stats import average

class AverageTests(unittest.TestCase):
def test_normal(self):
self.assertEqual(average([80, 90]), 85)

def test_zero_is_valid(self):
self.assertEqual(average([0, 100]), 50)

def test_invalid(self):
for values in [[], [-1], [101], [float("nan")]]:
with self.assertRaises(ValueError):
average(values)
python -m unittest discover -s tests -v

覆盖正常、零分、空列表和非法值。测试要表达业务约定,而不只是重复实现中的每一行代码。涉及临时文件时使用临时目录,不要让测试修改真实数据。

九、格式、静态检查与依赖复现

检查 解决什么问题 不能替代什么
格式化 缩进、空格和换行统一 业务正确性
lint 未用变量、部分易错写法 完整运行验证
类型检查 接口类型不匹配 输入是否合法
单元测试 已定义规则是否满足 所有真实场景
集成测试 模块连接后是否正常 人工观察和业务确认

可进一步使用 Ruff 等工具,但先理解每条提示。不要为了“让检查变绿”直接关闭所有规则。

pip freeze > requirements.txt 是当前环境的版本快照,可能包含无关包;在干净环境生成更有意义。pyproject.toml 中的依赖范围不等于完全锁定的环境,还要记录 Python 版本、平台和实际验证命令。不要提交 .venv/。

建议 .gitignore:

.venv/
__pycache__/
*.pyc
.env
build/
dist/
*.egg-info/

十、README 与交付

README 至少说明:项目做什么、需要什么版本、怎样安装、怎样运行、示例输入输出、怎样测试、已知限制。命令应能从一个明确目录完整执行,不能依赖作者记忆中的本机路径。

项目交付检查:克隆、建环境、安装、运行样例、运行测试、记录结果

下载完整项目 ZIP,解压进入目录后执行:

python -m pip install -e .
python -m scorelab 80 90
python -m unittest discover -s tests -v

预期:均分为 85.00,三个测试方法通过。再执行 python -m scorelab 120,预期失败并提示范围错误;失败也是接口的一部分。

十一、常见问题

问题 优先检查
ModuleNotFoundError 是否用当前解释器安装了项目,包名是否正确
pip 装了但不能导入 python -m pip --version 与解释器路径是否一致
换目录就读不到文件 是否使用未说明的相对路径
测试依赖执行顺序 是否共享可变全局状态或遗留文件
同伴结果不同 Python/依赖版本、输入数据、随机种子是否记录

十二、练习与完成标准

  1. 增加最高分函数及空输入测试,不要改变 average 的规则。
  2. 给入口增加 --precision,限制为 0~4 位小数,并测试非法值。
  3. 增加从 CSV 读取的入口;明确哪些行拒绝、哪些行忽略,保留行数报告。
  4. 找同伴在新环境按 README 跑通,记录其遇到的第一个问题并修正文档。

验收: 安装与入口统一,核心逻辑能独立测试,错误返回非零状态,版本和路径可说明,仓库没有密钥和虚拟环境。

十三、学习资料

← 继续探索学习路径反馈问题 ↗