Python项目结构与最佳实践

一、标准项目结构

1.1 小型项目结构

myproject/
├── README.md
├── LICENSE
├── setup.py
├── requirements.txt
├── .gitignore
├── myproject/
│ ├── __init__.py
│ ├── main.py
│ └── utils.py
└── tests/
├── __init__.py
└── test_main.py

1.2 中型项目结构

myproject/
├── README.md
├── LICENSE
├── setup.py
├── requirements.txt
├── requirements-dev.txt
├── .gitignore
├── .env.example
├── docs/
│ ├── conf.py
│ └── index.rst
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── cli.py
│ ├── config.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── user_service.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
└── tests/
├── __init__.py
├── conftest.py
├── unit/
│ └── test_user.py
└── integration/
└── test_user_service.py

1.3 大型项目结构

myproject/
├── README.md
├── LICENSE
├── CONTRIBUTING.md
├── CHANGELOG.md
├── setup.py
├── pyproject.toml
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
├── .gitignore
├── .env.example
├── .github/
│ └── workflows/
│ └── ci.yml
├── docs/
│ ├── conf.py
│ ├── index.rst
│ └── api/
├── src/
│ └── myproject/
│ ├── __init__.py
│ ├── __main__.py
│ ├── cli/
│ ├── api/
│ ├── models/
│ ├── services/
│ ├── repositories/
│ ├── utils/
│ └── config/
├── tests/
│ ├── __init__.py
│ ├── conftest.py
│ ├── unit/
│ ├── integration/
│ └── e2e/
├── scripts/
│ ├── setup.sh
│ └── deploy.sh
└── docker/
├── Dockerfile
└── docker-compose.yml

二、核心文件说明

2.1 README.md

# 项目名称

简短描述项目的功能和目的。

## 功能特性

- 特性1
- 特性2
- 特性3

## 安装

```bash
pip install myproject
```

## 快速开始

```python
from myproject import MyClass

obj = MyClass()
obj.do_something()
```

## 文档

完整文档请访问: https://myproject.readthedocs.io

## 贡献

欢迎贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)

## 许可证

MIT License

2.2 setup.py

from setuptools import setup, find_packages

with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()

setup(
name="myproject",
version="0.1.0",
author="Your Name",
author_email="you@example.com",
description="A short description",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yourusername/myproject",
packages=find_packages(where="src"),
package_dir={"": "src"},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
python_requires=">=3.8",
install_requires=[
"requests>=2.28.0",
"click>=8.0.0",
],
extras_require={
"dev": [
"pytest>=7.0.0",
"black>=22.0.0",
"flake8>=4.0.0",
"mypy>=0.950",
],
},
entry_points={
"console_scripts": [
"myproject=myproject.cli:main",
],
},
)

2.3 pyproject.toml

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "myproject"
version = "0.1.0"
description = "A short description"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
{name = "Your Name", email = "you@example.com"}
]
dependencies = [
"requests>=2.28.0",
"click>=8.0.0",
]

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=22.0.0",
"flake8>=4.0.0",
]

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

[tool.black]
line-length = 88
target-version = ['py38']

[tool.isort]
profile = "black"

[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]

2.4 .gitignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Virtual environments
venv/
env/
ENV/

# IDE
.vscode/
.idea/
*.swp
*.swo

# Testing
.pytest_cache/
.coverage
htmlcov/

# Environment
.env
.env.local

# OS
.DS_Store
Thumbs.db

三、包和模块组织

3.1 __init__.py的使用

# src/myproject/__init__.py

"""
MyProject - 项目描述

这是项目的主包。
"""

__version__ = "0.1.0"
__author__ = "Your Name"

# 导出公共API
from .main import MyClass, my_function

__all__ = ["MyClass", "my_function"]

3.2 相对导入 vs 绝对导入

# 推荐:绝对导入
from myproject.models.user import User
from myproject.services.user_service import UserService

# 避免:相对导入(除非在同一包内)
from ..models.user import User
from .user_service import UserService

3.3 循环导入的避免

# 不好:循环导入
# user.py
from myproject.services.user_service import UserService

class User:
def save(self):
UserService.save(self)

# user_service.py
from myproject.models.user import User

class UserService:
@staticmethod
def save(user: User):
pass

# 好:使用类型提示的字符串形式
# user.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from myproject.services.user_service import UserService

class User:
def save(self):
from myproject.services.user_service import UserService
UserService.save(self)

四、配置管理

4.1 使用配置类

# config.py
import os
from dataclasses import dataclass

@dataclass
class Config:
"""基础配置"""
DEBUG: bool = False
TESTING: bool = False
SECRET_KEY: str = os.getenv('SECRET_KEY', 'dev-secret-key')
DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///app.db')

@dataclass
class DevelopmentConfig(Config):
"""开发环境配置"""
DEBUG: bool = True

@dataclass
class ProductionConfig(Config):
"""生产环境配置"""
DEBUG: bool = False

@dataclass
class TestingConfig(Config):
"""测试环境配置"""
TESTING: bool = True
DATABASE_URL: str = 'sqlite:///:memory:'

# 配置字典
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}

def get_config(env=None):
"""获取配置"""
env = env or os.getenv('FLASK_ENV', 'default')
return config[env]()

4.2 使用.env文件

# .env.example
DEBUG=true
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost/dbname
API_KEY=your-api-key

# 加载.env
from dotenv import load_dotenv
load_dotenv()

五、日志配置

5.1 集中式日志配置

# logging_config.py
import logging
import logging.config

LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'standard',
'stream': 'ext://sys.stdout'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'standard',
'filename': 'logs/app.log',
'maxBytes': 10485760,
'backupCount': 5
}
},
'loggers': {
'': {
'level': 'INFO',
'handlers': ['console', 'file']
},
'myproject': {
'level': 'DEBUG',
'handlers': ['console', 'file'],
'propagate': False
}
}
}

def setup_logging():
logging.config.dictConfig(LOGGING_CONFIG)

六、测试组织

6.1 测试结构

tests/
├── __init__.py
├── conftest.py # pytest fixtures
├── unit/ # 单元测试
│ ├── __init__.py
│ ├── test_models.py
│ └── test_services.py
├── integration/ # 集成测试
│ ├── __init__.py
│ └── test_api.py
└── e2e/ # 端到端测试
├── __init__.py
└── test_workflows.py

6.2 conftest.py

# tests/conftest.py
import pytest
from myproject import create_app
from myproject.database import db

@pytest.fixture
def app():
"""创建测试应用"""
app = create_app('testing')
with app.app_context():
db.create_all()
yield app
db.drop_all()

@pytest.fixture
def client(app):
"""创建测试客户端"""
return app.test_client()

@pytest.fixture
def sample_user():
"""创建示例用户"""
return {
'username': 'testuser',
'email': 'test@example.com'
}

七、文档组织

7.1 使用Sphinx

# docs/conf.py
project = 'MyProject'
copyright = '2024, Your Name'
author = 'Your Name'

extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.viewcode',
]

html_theme = 'sphinx_rtd_theme'

7.2 文档字符串

def calculate_total(items: list, tax_rate: float = 0.1) -> float:
"""
计算订单总额。

Args:
items: 订单项目列表,每个项目包含price和quantity
tax_rate: 税率,默认为0.1(10%)

Returns:
包含税费的订单总额

Raises:
ValueError: 如果items为空或tax_rate为负数

Example:
>>> items = [{'price': 10, 'quantity': 2}]
>>> calculate_total(items)
22.0
"""
if not items:
raise ValueError("订单项目不能为空")
if tax_rate < 0:
raise ValueError("税率不能为负数")

subtotal = sum(item['price'] * item['quantity'] for item in items)
return subtotal * (1 + tax_rate)

八、依赖管理

8.1 requirements文件组织

# requirements/base.txt
requests>=2.28.0
click>=8.0.0
python-dotenv>=0.20.0

# requirements/dev.txt
-r base.txt
pytest>=7.0.0
pytest-cov>=3.0.0
black>=22.0.0
flake8>=4.0.0
mypy>=0.950

# requirements/prod.txt
-r base.txt
gunicorn>=20.1.0

8.2 使用pip-tools

# 安装
pip install pip-tools

# requirements.in
requests
click
python-dotenv

# 生成锁定文件
pip-compile requirements.in

# 安装
pip-sync requirements.txt

九、CI/CD配置

9.1 GitHub Actions

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, '3.10']

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements/dev.txt

- name: Lint
run: |
flake8 src tests
black --check src tests
mypy src

- name: Test
run: |
pytest --cov=myproject tests/

- name: Upload coverage
uses: codecov/codecov-action@v2

十、Docker化

10.1 Dockerfile

# Dockerfile
FROM python:3.10-slim

WORKDIR /app

# 安装依赖
COPY requirements/prod.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# 复制代码
COPY src/ .

# 创建非root用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:app"]

10.2 docker-compose.yml

version: '3.8'

services:
web:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db

db:
image: postgres:14
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:

十一、版本控制最佳实践

11.1 .gitignore模板

# 使用gitignore.io生成
# https://www.toptal.com/developers/gitignore/api/python

11.2 提交消息规范

# 格式
():

 



# 类型
feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式
refactor: 重构
test: 测试
chore: 构建/工具

# 示例
feat(auth): 添加JWT认证

实现了基于JWT的用户认证系统,包括:
- 登录端点
- 令牌刷新
- 令牌验证中间件

Closes #123

十二、项目检查清单

- [ ] README.md完整
- [ ] LICENSE文件
- [ ] .gitignore配置
- [ ] requirements.txt
- [ ] 测试覆盖率>80%
- [ ] CI/CD配置
- [ ] 文档完整
- [ ] 类型提示
- [ ] 日志配置
- [ ] 错误处理
- [ ] 安全检查
- [ ] 性能测试
- [ ] Docker化
- [ ] 环境变量配置

十三、总结

良好的项目结构是可维护代码的基础。遵循标准的目录布局,合理组织代码,配置完善的开发工具链,可以大大提高开发效率和代码质量。记住,项目结构应该随着项目规模的增长而演进,从简单开始,逐步完善。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐