Python 项目结构最佳实践:配置、请求、业务分开写,后期真的省事
适合个人开发者、AI 工具作者、脚本自动化玩家。
如果你现在的项目还把 API Key、请求逻辑、业务逻辑全写在一个文件里,这篇文章可以直接改掉你的写法。
为什么项目一开始就要拆结构?
很多人做项目的时候,第一版通常都很简单:
- 一个
main.py - 里面直接写请求
- Key 也写死在里面
- 业务逻辑和接口调用混在一起
这种写法能跑,但很快就会出现问题:
- 代码越来越乱
- 修改一个地方要翻很多行
- 换模型、换接口、换配置都很麻烦
- 出错后不好排查
所以如果你做的是 AI 工具、自动化脚本、API 接入项目,最好从一开始就把结构拆开。
这篇文章直接给你一个适合个人开发者的最小结构方案。
一、先说结论:推荐的项目结构
你可以把项目拆成这几块:
project/
├── .env
├── config.py
├── llm_client.py
├── main.py
├── requirements.txt
└── logs/
每个文件干什么?
.env:放密钥、地址、模型名config.py:统一读取配置llm_client.py:封装 API 调用main.py:写业务逻辑logs/:放日志
这种结构不复杂,但后期很好维护。
二、为什么不建议把所有代码写在一个文件里?
1. 维护麻烦
你一旦把 API 调用、错误处理、业务逻辑、配置读取全写在一起,后面改起来会很痛苦。
2. 不方便复用
如果你以后还想做第二个项目,很多代码没法直接拿过来。
3. 不利于排错
出问题时,你根本不容易判断是配置错了,还是请求错了,还是业务逻辑错了。
4. 不适合扩展
你后面一旦加重试、缓存、日志、限流,这种单文件结构会越来越乱。
三、.env 里放什么最合适?
建议把这些放进去:
API_KEY=***
BASE_URL=https://your-api-domain.com/v1
MODEL=your-model-name
TIMEOUT=20
MAX_RETRIES=3
这样做的好处
- 不把敏感信息写死在代码里
- 本地和线上可以切换配置
- 修改参数时不用动业务代码
四、config.py 怎么写?
config.py 的作用就是统一读取配置并做校验。
import os
from dotenv import load_dotenv
load_dotenv()
def get_config():
api_key = os.getenv("API_KEY")
base_url = os.getenv("BASE_URL")
model = os.getenv("MODEL")
timeout = int(os.getenv("TIMEOUT", "20"))
max_retries = int(os.getenv("MAX_RETRIES", "3"))
if not api_key:
raise ValueError("API_KEY is required")
if not base_url:
raise ValueError("BASE_URL is required")
if not model:
raise ValueError("MODEL is required")
return {
"api_key": api_key,
"base_url": base_url,
"model": model,
"timeout": timeout,
"max_retries": max_retries,
}
这个文件的作用
- 统一读取环境变量
- 启动时提前发现问题
- 避免 Key 为空还继续跑
五、llm_client.py 怎么封装最舒服?
这里建议把所有 API 调用都放在一个地方。
import time
from openai import OpenAI
from openai import APIError, APIConnectionError, APITimeoutError, RateLimitError
from config import get_config
config = get_config()
client = OpenAI(
api_key=config["api_key"],
base_url=config["base_url"],
timeout=float(config["timeout"]),
)
def ask_llm(prompt: str) -> str:
last_error = None
for attempt in range(1, config["max_retries"] + 1):
try:
response = client.chat.completions.create(
model=config["model"],
messages=[
{"role": "system", "content": "你是一个专业的技术助手。"},
{"role": "user", "content": prompt},
],
)
return response.choices[0].message.content
except (APIConnectionError, APITimeoutError, RateLimitError, APIError) as e:
last_error = e
if attempt < config["max_retries"]:
wait = 2 * attempt
print(f"第 {attempt} 次失败,{wait} 秒后重试:{e}")
time.sleep(wait)
else:
print(f"重试结束,最终失败:{e}")
raise RuntimeError(f"请求失败:{last_error}")
为什么这样封装?
因为你后面只要改这个文件,就能影响整个项目的调用行为。
六、main.py 只负责业务逻辑
main.py 不要再管配置,不要再管重试,不要再管细节调用。
from llm_client import ask_llm
def main():
question = "给我写一个 Flask 接口示例"
answer = ask_llm(question)
print(answer)
if __name__ == "__main__":
main()
这样写的好处
- 主入口非常清楚
- 业务逻辑和基础设施分离
- 后面加更多功能也不乱
七、这套结构适合哪些项目?
特别适合这些场景:
- AI 工具站
- 自动化脚本
- Agent 工作流
- 文本生成项目
- 个人效率工具
- 技术副业项目
如果你后面还打算继续迭代,这种拆法会比单文件强很多。
八、几个很容易踩坑的地方
1. 不要把 Key 写死在代码里
一定放 .env。
2. 不要把请求逻辑散落在各处
统一封装到一个文件里。
3. 不要把业务和基础设施混在一起
main.py 只做流程控制。
4. 不要忘了做配置校验
启动时报错总比运行半天才发现问题好。
九、如果你后面要扩展,还可以继续加什么?
当项目变大后,你还可以继续加:
logger.py:统一日志cache.py:缓存结果retry.py:单独抽重试逻辑api/:不同接口模块化tests/:测试用例
但对于个人开发者来说,先把上面这套最小结构跑通就够了。
十、结语
很多项目后面不好维护,不是因为功能太复杂,而是一开始就把所有东西写在一起。
如果你能从第一天就把:
- 配置
- 请求
- 业务
- 日志
分开处理,后面会省很多时间。
如果你也在做 AI 工具、脚本自动化或者个人项目,可以留言或私信,我可以把我整理好的项目模板发给你。
免责声明
本文内容仅用于技术交流与经验分享,不构成任何商业承诺。具体使用效果请以实际测试为准。
更多推荐


所有评论(0)