Python 虚拟环境与项目结构整理


一、背景

上一篇整理了 Python 常用标准库。

这一篇继续整理 Python 项目开发中非常基础,但又很容易被忽略的内容:

  1. 虚拟环境
  2. requirements.txt
  3. .env 配置文件
  4. 项目目录如何组织
  5. 如何写一个可复用的小工具包

如果只是写几十行临时脚本,可以把所有代码都放在一个 .py 文件里。

但是当脚本越来越多,或者一个项目需要多人维护时,就需要关注环境隔离、依赖管理、配置管理和目录结构。

二、为什么需要虚拟环境

如果所有 Python 项目都共用系统 Python 环境,很容易出现依赖冲突。

例如:

项目 A 需要 requests==2.28.0
项目 B 需要 requests==2.31.0

如果两个项目都安装在系统 Python 里,就可能互相影响。

虚拟环境的作用是:给每个项目创建一套独立的依赖目录。

这样不同项目可以使用不同版本的第三方库。

三、venv 创建虚拟环境

Python3 自带 venv 模块。

进入项目目录后执行:

python -m venv .venv

这会在当前项目下创建一个 .venv 目录。

目录大概如下:

.venv/
  bin/
  include/
  lib/
  pyvenv.cfg

Windows 下目录结构会有些不同,一般会有:

.venv/
  Scripts/
  Lib/
  pyvenv.cfg

四、激活虚拟环境

macOS 或 Linux:

source .venv/bin/activate

Windows:

.venv\Scripts\activate

激活后,命令行前面通常会出现类似:

(.venv)

这表示当前 shell 已经进入虚拟环境。

查看当前 Python:

which python
python --version
python -m pip --version

Windows 使用:

where python
python --version
python -m pip --version

退出虚拟环境:

deactivate

五、在虚拟环境中安装依赖

激活虚拟环境后安装依赖:

python -m pip install requests

确认安装位置:

python -m pip show requests

查看当前环境所有依赖:

python -m pip list

这里建议一直使用:

python -m pip

而不是直接使用:

pip

这样可以减少 pip 和 Python 环境不一致的问题。

六、requirements.txt

requirements.txt 用来记录项目依赖。

安装几个依赖后,可以执行:

python -m pip freeze > requirements.txt

文件内容类似:

requests==2.31.0
pymysql==1.1.0
python-dotenv==1.0.0

其他机器上安装依赖:

python -m pip install -r requirements.txt

这样别人拿到项目后,不需要一个个手动安装依赖。

七、requirements.txt 的使用建议

pip freeze 会导出当前环境里的所有包。

如果当前环境已经安装了很多无关依赖,生成出来的 requirements.txt 也会很乱。

所以建议:

  1. 每个项目单独创建虚拟环境
  2. 只在项目虚拟环境里安装项目需要的依赖
  3. 在干净环境中生成 requirements.txt
  4. 不要把系统 Python 的依赖直接导出来

如果只是手动维护,也可以自己写:

requests==2.31.0
pymysql==1.1.0
python-dotenv==1.0.0

比起导出一大堆无关依赖,手写核心依赖有时更清楚。

八、.env 配置文件

项目中经常会有一些配置,例如:

  1. 数据库地址
  2. 数据库用户名
  3. 数据库密码
  4. 接口地址
  5. 运行环境

这些内容不建议直接写死在代码里。

可以放到 .env 文件中。

例如:

MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=root
MYSQL_DATABASE=test
APP_ENV=dev

然后在代码里读取。

九、使用 os 读取环境变量

如果环境变量已经存在,可以直接使用 os.getenv()

import os

mysql_host = os.getenv("MYSQL_HOST", "127.0.0.1")
mysql_port = int(os.getenv("MYSQL_PORT", "3306"))

print(mysql_host)
print(mysql_port)

第二个参数是默认值。

这种写法适合服务器上通过环境变量传配置。

十、使用 python-dotenv 读取 .env

本地开发时,可以使用 python-dotenv 读取 .env 文件。

安装:

python -m pip install python-dotenv

代码:

import os
from dotenv import load_dotenv


load_dotenv()

mysql_host = os.getenv("MYSQL_HOST", "127.0.0.1")
mysql_port = int(os.getenv("MYSQL_PORT", "3306"))

print(mysql_host)
print(mysql_port)

这样本地开发时可以把配置写在 .env 中,代码中统一从环境变量读取。

十一、.env 不要提交到 Git

.env 中可能包含密码、Token、密钥等敏感信息。

所以一般要加入 .gitignore

.env
.venv/

可以提供一个 .env.example 作为模板。

例如:

MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=please_change
MYSQL_DATABASE=test
APP_ENV=dev

提交 .env.example,不要提交真实 .env

十二、项目目录如何组织

一个简单 Python 项目可以这样组织:

demo_project/
  README.md
  requirements.txt
  .env.example
  .gitignore
  main.py
  config.py
  utils/
    __init__.py
    file_utils.py
    date_utils.py
  logs/
  data/

说明:

路径 作用
README.md 项目说明
requirements.txt 项目依赖
.env.example 配置模板
.gitignore Git 忽略规则
main.py 程序入口
config.py 配置读取
utils/ 通用工具函数
logs/ 日志目录
data/ 数据文件目录

小项目不需要一开始就设计得很复杂。

先做到:

  1. 入口清楚
  2. 配置独立
  3. 工具函数独立
  4. 依赖可复现
  5. 敏感信息不进 Git

十三、main.py 作为入口

main.py 可以作为程序入口。

from utils.date_utils import now_text


def main():
    print("程序开始")
    print(now_text())


if __name__ == "__main__":
    main()

if __name__ == "__main__": 的作用是:

  1. 直接运行当前文件时执行 main()
  2. 被其他模块导入时不自动执行 main()

这是 Python 项目中非常常见的写法。

十四、config.py 读取配置

可以把配置读取统一放在 config.py

import os
from dotenv import load_dotenv


load_dotenv()


MYSQL_CONFIG = {
    "host": os.getenv("MYSQL_HOST", "127.0.0.1"),
    "port": int(os.getenv("MYSQL_PORT", "3306")),
    "user": os.getenv("MYSQL_USER", "root"),
    "password": os.getenv("MYSQL_PASSWORD", ""),
    "database": os.getenv("MYSQL_DATABASE", "test"),
}

其他模块需要数据库配置时,直接导入:

from config import MYSQL_CONFIG


print(MYSQL_CONFIG)

这样配置读取逻辑不会散落在各个文件中。

十五、写一个可复用的小工具包

假设项目里经常要处理文件,可以写一个 utils/file_utils.py

from pathlib import Path


def read_text(path):
    return Path(path).read_text(encoding="utf-8")


def write_text(path, content):
    file_path = Path(path)
    file_path.parent.mkdir(parents=True, exist_ok=True)
    file_path.write_text(content, encoding="utf-8")

再写一个 utils/date_utils.py

from datetime import datetime


def now_text():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

utils/__init__.py 可以先留空。

使用:

from utils.file_utils import read_text, write_text
from utils.date_utils import now_text


write_text("data/hello.txt", now_text())
print(read_text("data/hello.txt"))

十六、工具函数怎么拆

工具函数不要一开始就拆得太碎。

比较实用的拆分方式是按功能分类:

utils/
  file_utils.py
  date_utils.py
  json_utils.py
  db_utils.py
  log_utils.py

每个文件只放相关能力。

例如:

文件 适合放什么
file_utils.py 文件读写、目录创建、文件遍历
date_utils.py 日期格式化、时间转换
json_utils.py JSON 读写
db_utils.py 数据库连接、查询封装
log_utils.py 日志初始化

如果某个函数只在一个地方使用,不一定要马上抽到 utils 里。

当它被多个模块重复使用时,再抽出来更合适。

十七、不要把所有东西都放 utils

utils 很方便,但也容易变成杂物间。

不建议把业务逻辑都放到 utils 里。

例如一个爬虫项目,可以这样组织:

spider_project/
  main.py
  config.py
  crawler/
    __init__.py
    client.py
    parser.py
    pipeline.py
  utils/
    __init__.py
    file_utils.py
    log_utils.py

说明:

  1. crawler/client.py 放请求逻辑
  2. crawler/parser.py 放解析逻辑
  3. crawler/pipeline.py 放保存逻辑
  4. utils/ 只放通用工具

这样项目结构会比全部堆在 utils 中更清楚。

十八、.gitignore 常见配置

Python 项目中常见 .gitignore

.venv/
__pycache__/
*.pyc
.env
.DS_Store
logs/
*.log

说明:

规则 说明
.venv/ 虚拟环境目录
__pycache__/ Python 字节码缓存
*.pyc 编译缓存文件
.env 本地敏感配置
.DS_Store macOS 系统文件
logs/ 日志目录
*.log 日志文件

虚拟环境和日志都不应该提交到 Git。

十九、一个完整小项目示例

最终目录:

demo_project/
  README.md
  requirements.txt
  .env.example
  .gitignore
  main.py
  config.py
  utils/
    __init__.py
    date_utils.py
    file_utils.py

.gitignore

.venv/
__pycache__/
*.pyc
.env
*.log

.env.example

APP_ENV=dev
OUTPUT_FILE=data/output.txt

requirements.txt

python-dotenv==1.0.0

config.py

import os
from dotenv import load_dotenv


load_dotenv()


APP_ENV = os.getenv("APP_ENV", "dev")
OUTPUT_FILE = os.getenv("OUTPUT_FILE", "data/output.txt")

utils/date_utils.py

from datetime import datetime


def now_text():
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

utils/file_utils.py

from pathlib import Path


def write_text(path, content):
    file_path = Path(path)
    file_path.parent.mkdir(parents=True, exist_ok=True)
    file_path.write_text(content, encoding="utf-8")

main.py

from config import APP_ENV, OUTPUT_FILE
from utils.date_utils import now_text
from utils.file_utils import write_text


def main():
    content = f"env={APP_ENV}, time={now_text()}\n"
    write_text(OUTPUT_FILE, content)
    print("写入完成:", OUTPUT_FILE)


if __name__ == "__main__":
    main()

运行:

python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
python main.py

二十、总结

这一篇主要整理了 Python 虚拟环境和项目结构。

需要重点记住:

  1. 每个项目尽量使用独立虚拟环境
  2. 使用 python -m venv .venv 创建虚拟环境
  3. 使用 requirements.txt 固定依赖
  4. 本地敏感配置放 .env,不要提交到 Git
  5. 可以提交 .env.example 作为配置模板
  6. 小项目可以从 main.pyconfig.pyutils/ 开始组织
  7. 工具函数按功能拆分,不要把业务逻辑都塞进 utils
  8. .venv/__pycache__/.env、日志文件都应该放进 .gitignore

把这些基础做好后,后续不管是写爬虫、数据处理脚本,还是写 Web 服务,项目都会更容易维护。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录