一个 main.py,读 Excel、调接口、写数据库,跑通就行。后来需求多了:加配置、加日志、加定时任务、加命令行参数、加异常重试。再后来,没人敢改了。
问题不在 Python 语法,而在项目一直停留在“脚本堆积”阶段。
到了 2026 年,Python 早就不只是写小脚本的语言了。数据处理、自动化平台、内部工具、AI 工作流,很多都靠 Python 跑。项目一旦要长期维护,就不能再把所有东西塞进一个 main.py。
脚本和项目的区别
脚本追求的是“今天能跑”。
项目追求的是“三个月后别人还能改”。
很多小工具不是因为功能复杂才难维护,而是因为最开始没有边界。读取数据、业务处理、结果输出、异常处理混在一起,改一行影响一片。
先把目录结构立起来
一个小工具也可以有清晰结构,不需要一上来搞得很重。
比如一个订单数据导出工具,可以这样放:
order-exporter/ pyproject.toml README.md src/ order_exporter/ __init__.py cli.py config.py service.py repository.py exporter.py tests/ test_service.py
这里有几个简单规则:
这样拆完之后,main.py 就算存在,也只应该是一个入口,而不是垃圾桶。
配置不要写死在代码里
脚本阶段最常见的写法是这样:
DB_HOST = "10.0.0.12"TOKEN = "abc123"EXPORT_PATH = "/tmp/data.xlsx"
这类代码迟早会出问题。换环境要改代码,密钥容易提交到仓库,线上线下配置也分不清。
更稳的方式是把配置集中起来:
from pydantic_settings import BaseSettingsclassSettings(BaseSettings): db_host: str api_token: str export_path: str = "./output.xlsx"classConfig: env_file = ".env"settings = Settings()
本地用 .env,线上走环境变量。代码只关心 settings.db_host,不要关心配置从哪里来。
这一步很小,但能省掉很多低级事故。
依赖要隔离,也要锁住
很多 Python 项目最怕一句话:
“我这里能跑啊。”
通常是因为每个人机器上的依赖版本不一样。A 装的是 pandas 2.0,B 装的是 pandas 2.2,线上又是另一个版本。
现在做 Python 项目,至少要做到两点:
使用虚拟环境锁定依赖版本
简单项目可以用 venv + requirements.txt:
python -m venv .venvsource .venv/bin/activatepip install -r requirements.txt
更规范一点,可以用 uv 或 Poetry 管理依赖。工具不是重点,重点是不要污染全局环境,也不要让依赖版本漂移。
日志别再全靠 print
print 在调试时很方便,但项目长期跑起来后,它不够用。
你需要知道:
可以先用 Python 自带的 logging:
import logginglogging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s")logger = logging.getLogger(__name__)logger.info("start export orders")
不要小看日志。很多内部工具没有监控,日志就是最后的救命绳。
测试先从核心逻辑开始
不少人一听测试就头大,觉得要把所有代码覆盖一遍。
没必要一开始就这么重。先测最容易出错、最值得保护的部分:业务逻辑。
比如金额计算、状态流转、字段映射、过滤规则,这些都应该从脚本里抽出来,变成可测试函数。
defshould_export(order):return order.status == "PAID"and order.amount > 0
对应测试:
deftest_should_export_paid_order(): order = Order(status="PAID", amount=100)assert should_export(order) isTrue
别先测数据库,别先测外部 API。先把纯逻辑测住,收益最大。
CLI 入口要正规一点
很多脚本靠改源码参数运行:
start_date = "2026-01-01"end_date = "2026-01-31"
这不是工具,这是半成品。
至少应该支持命令行参数:
import typerapp = typer.Typer()@app.command()defexport(start_date: str, end_date: str): print(f"export from {start_date} to {end_date}")if __name__ == "__main__": app()
运行时就可以这样:
python -m order_exporter.cli export \ --start-date 2026-01-01 \ --end-date 2026-01-31
工具一旦可以被命令行稳定调用,就更容易接入定时任务、CI、调度平台。
一个简单的演进顺序
如果你手里已经有一个几百行的 main.py,不用一次性重构完。可以按这个顺序来:
别一上来就大改目录。先切出边界,再慢慢搬代码。
最后
Python 很适合快速开始,但快速开始不等于一直随便写。
一个小工具,只要会被反复运行、会被别人接手、会影响真实业务,就应该有基本的工程化结构。
别等 main.py 长到 2000 行才重构。那时候你面对的已经不是脚本,而是一团没有名字的历史包袱。