环境隔离与依赖管理
在实际项目中,我们经常遇到不同项目需要不同Python版本或依赖包的情况。曾经遇到过一个典型问题:一个使用TensorFlow 1.x的旧项目和一个需要TensorFlow 2.x的新项目同时在开发,如果不做好环境隔离,就会导致依赖冲突。
推荐方案:
- 使用pyenv管理多版本Python
- 使用venv创建虚拟环境
- 使用requirements.txt或Pipfile精确记录依赖
# 创建虚拟环境的最佳实践
python -m venv project_env
source project_env/bin/activate # Linux/Mac
# project_env\Scripts\activate # Windows
# 生成精确的依赖文件
pip freeze > requirements.txt
# 安装时指定版本
pip install -r requirements.txt
代码结构与模块化设计
良好的项目结构是维护性的基础。曾经接手过一个几千行代码都放在单个文件中的项目,每次修改都如履薄冰。
推荐的项目结构:
my_project/
├── src/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── data_processor.py
│ │ └── model.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
├── docs/
├── setup.py
└── requirements.txt
模块设计原则:
- 单一职责:每个模块只负责一个明确的功能
- 低耦合高内聚:模块间依赖最小化,模块内部功能相关性强
- 清晰的接口:通过
__init__.py暴露主要功能
配置管理的艺术
硬编码配置是项目维护的噩梦。曾经因为数据库连接字符串硬编码在多个文件中,迁移数据库时差点崩溃。
配置管理方案:
# config.py
import os
from dataclasses import dataclass
@dataclass
class Config:
DEBUG: bool = os.getenv('DEBUG', 'False').lower() == 'true'
DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///default.db')
MAX_WORKERS: int = int(os.getenv('MAX_WORKERS', '4'))
# 使用示例
from config import Config
cfg = Config()
环境区分:
- 开发环境:使用.env文件或环境变量
- 测试环境:独立的配置文件
- 生产环境:通过环境变量或配置服务
异常处理与日志记录
没有良好的异常处理和日志记录,线上问题排查就像大海捞针。曾经因为一个简单的网络超时没有正确处理,导致整个服务雪崩。
异常处理最佳实践:
import logging
from typing import Optional
logger = logging.getLogger(__name__)
def process_data(data: list) -> Optional[dict]:
try:
# 业务逻辑
result = complex_operation(data)
return result
except ValueError as e:
logger.warning(f"数据格式错误: {e}")
return None
except ConnectionError as e:
logger.error(f"网络连接失败: {e}")
raise # 重新抛出,让上层处理
except Exception as e:
logger.exception(f"未知错误: {e}")
return None
日志配置:
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler(sys.stdout)
]
)
测试与质量保证
没有测试的代码就像没有安全网的高空作业。曾经因为一个简单的边界条件没测试,导致生产环境数据损坏。
测试策略:
# tests/test_core.py
import pytest
from src.core.data_processor import DataProcessor
class TestDataProcessor:
def test_process_valid_data(self):
processor = DataProcessor()
data = [1, 2, 3]
result = processor.process(data)
assert result == [2, 4, 6]
def test_process_empty_data(self):
processor = DataProcessor()
with pytest.raises(ValueError):
processor.process([])
@pytest.mark.parametrize("input_data,expected", [
([1], [2]),
([1, 2, 3], [2, 4, 6]),
])
def test_process_multiple_cases(self, input_data, expected):
processor = DataProcessor()
assert processor.process(input_data) == expected
质量保证工具链:
- 静态检查:flake8, mypy
- 代码格式化:black, isort
- 安全扫描:bandit
- 测试覆盖:pytest-cov
持续集成与部署
手动部署容易出错且效率低下。曾经因为忘记某个部署步骤,导致服务中断半小时。
GitHub Actions示例:
name: Python CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=src tests/
这些实践经验来自多年Python开发的血泪教训,希望能帮助大家避开我踩过的坑。
暂无评论