Python代码质量之从规范到自动化检查全过程
作者:牧码人王木木
本文介绍了提高Python代码质量的十个关键方面,包括清晰的命名、一致性、注释、模块化、错误处理、测试、代码简洁、版本控制、性能优化和文档编写,需要的朋友可以参考下
1. 技术分析
1.1 代码质量维度
| 维度 | 描述 | 工具 |
|---|---|---|
| 代码风格 | PEP 8规范 | black, isort |
| 类型检查 | 类型注解检查 | mypy |
| 代码规范 | 最佳实践 | flake8, pylint |
| 安全检查 | 潜在漏洞 | bandit, safety |
| 测试覆盖 | 代码测试比例 | coverage |
1.2 工具对比
| 工具 | 功能 | 性能 | 学习曲线 |
|---|---|---|---|
| black | 代码格式化 | 快 | 低 |
| flake8 | 代码检查 | 快 | 低 |
| mypy | 类型检查 | 中 | 中 |
| pylint | 全面检查 | 慢 | 高 |
| ruff | 快速linting | 极快 | 低 |
2. 核心功能实现
2.1 代码格式化配置
# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py39', 'py310', 'py311']
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.venv
| build
| dist
)/
'''
[tool.isort]
profile = "black"
line_length = 88
known_first_party = ["src"]
skip = [".venv", "build", "dist"]
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
ignore_missing_imports = true
[tool.ruff]
line-length = 88
target-version = "py39"
[tool.ruff.lint]
select = ["E", "F", "W", "I", "N", "UP", "B", "C4"]
ignore = ["E501"] # 行长度由black处理
[tool.coverage.run]
source = ["src"]
omit = ["*/tests/*", "*/test_*.py"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if __name__ == .__main__.:",
"raise AssertionError()",
]2.2 单元测试实践
import pytest
from typing import List, Optional
class DataValidator:
"""数据验证器"""
@staticmethod
def validate_email(email: str) -> bool:
"""验证邮箱格式"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))
@staticmethod
def validate_positive(value: float) -> bool:
"""验证正数"""
return value > 0
@staticmethod
def validate_in_range(value: float, min_val: float, max_val: float) -> bool:
"""验证范围"""
return min_val <= value <= max_val
class TestDataValidator:
"""数据验证器测试"""
@pytest.mark.parametrize("email,expected", [
("test@example.com", True),
("user.name@domain.co.uk", True),
("invalid-email", False),
("@domain.com", False),
("user@", False),
("", False),
])
def test_validate_email(self, email, expected):
assert DataValidator.validate_email(email) == expected
@pytest.mark.parametrize("value,expected", [
(1.0, True),
(0.0, False),
(-1.0, False),
(100.5, True),
])
def test_validate_positive(self, value, expected):
assert DataValidator.validate_positive(value) == expected
def test_validate_in_range(self):
assert DataValidator.validate_in_range(5, 0, 10) == True
assert DataValidator.validate_in_range(0, 0, 10) == True
assert DataValidator.validate_in_range(10, 0, 10) == True
assert DataValidator.validate_in_range(-1, 0, 10) == False
assert DataValidator.validate_in_range(11, 0, 10) == False
class TestEdgeCases:
"""边界情况测试"""
def test_empty_string(self):
assert DataValidator.validate_email("") == False
def test_unicode_email(self):
assert DataValidator.validate_email("用户@例子.广告") == False
def test_very_long_email(self):
long_email = "a" * 100 + "@example.com"
# 应该能处理但可能返回False(取决于具体实现)
result = DataValidator.validate_email(long_email)
assert isinstance(result, bool)
2.3 Mock与测试隔离
from unittest.mock import Mock, patch, MagicMock
import pytest
class APIClient:
"""API客户端"""
def __init__(self, base_url: str):
self.base_url = base_url
self.session = None
def fetch(self, endpoint: str) -> dict:
"""获取数据"""
import requests
response = requests.get(f"{self.base_url}/{endpoint}")
return response.json()
class TestAPIClient:
"""API客户端测试"""
@patch('requests.get')
def test_fetch_success(self, mock_get):
"""测试成功获取"""
mock_response = Mock()
mock_response.json.return_value = {"status": "success", "data": [1, 2, 3]}
mock_get.return_value = mock_response
client = APIClient("https://api.example.com")
result = client.fetch("users")
assert result["status"] == "success"
assert result["data"] == [1, 2, 3]
mock_get.assert_called_once_with("https://api.example.com/users")
@patch('requests.get')
def test_fetch_error(self, mock_get):
"""测试获取失败"""
mock_get.side_effect = ConnectionError("Network error")
client = APIClient("https://api.example.com")
with pytest.raises(ConnectionError):
client.fetch("users")
def test_with_fixture(self, mock_get):
"""使用fixture的测试"""
# fixture在conftest.py中定义
result = self.client.fetch("users")
assert "status" in result
2.4 性能测试
import pytest
import time
class TestPerformance:
"""性能测试"""
def test_sort_performance(self):
"""测试排序性能"""
import random
# 生成大量数据
data = [random.randint(0, 10000) for _ in range(10000)]
start = time.perf_counter()
sorted_data = sorted(data)
elapsed = time.perf_counter() - start
# 应该在1秒内完成
assert elapsed < 1.0, f"排序耗时 {elapsed:.2f}s,超过1秒"
# 验证排序正确性
assert sorted_data == sorted(data)
@pytest.mark.benchmark
def test_list_comprehension_performance(self, benchmark):
"""基准测试列表推导式"""
result = benchmark(lambda: [i**2 for i in range(10000)])
assert len(result) == 10000
# conftest.py
def pytest_configure(config):
config.addinivalue_line("markers", "benchmark: mark test as a benchmark")
@pytest.fixture
def sample_data():
"""示例数据fixture"""
return [i for i in range(100)]
3. 持续集成配置
3.1 pre-commit配置
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3.10
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.261
hooks:
- id: ruff
args: ["--fix"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0
hooks:
- id: mypy
additional_dependencies: [types-all]3.2 GitHub Actions CI
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint with ruff
run: ruff check src/
- name: Format check with black
run: black --check src/
- name: Type check with mypy
run: mypy src/
- name: Test with pytest
run: |
coverage run -m pytest tests/
coverage report --fail-under=80
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml4. 代码质量指标
4.1 覆盖率报告
# 运行测试并生成覆盖率报告 $ coverage run -m pytest tests/ $ coverage report -m Name Stmts Miss Cover Missing ----------------------------------------------------- src/validators.py 45 5 89% 23,45,67 src/models.py 78 12 85% 34,56,78 tests/test_validators.py 60 0 100% - ----------------------------------------------------- TOTAL 183 17 91%
4.2 复杂度分析
# 使用radon进行复杂度分析
from radon.metrics import mi_visit, h_visit
from radon.complexity import cc_visit
def analyze_complexity(filepath: str):
"""代码复杂度分析"""
with open(filepath, 'r') as f:
source = f.read()
# 圈复杂度
complexity = cc_visit(source)
print("圈复杂度:")
for item in complexity:
if item.classname:
name = f"{item.classname}.{item.name}"
else:
name = item.name
print(f" {name}: {item.complexity}")
# 维护性指数
mi = mi_visit(source, multi=True)
print(f"\n维护性指数: {mi:.1f}")
# Halstead指标
from radon.metrics import h_visit
halstead = h_visit(source)
print(f"难度: {halstead.difficulty:.1f}")
5. 最佳实践
5.1 代码审查清单
- [ ] 代码符合PEP 8规范 - [ ] 函数和类有docstring - [ ] 类型注解完整 - [ ] 单元测试覆盖关键逻辑 - [ ] 没有硬编码的魔法数字 - [ ] 错误处理适当 - [ ] 没有安全漏洞 - [ ] 性能符合要求
5.2 提交前检查
#!/bin/bash # pre-commit-check.sh set -e echo "运行代码检查..." # 格式化 black --check src/ echo "✓ 格式化检查通过" # 检查import isort --check-only --diff src/ echo "✓ import检查通过" # Lint ruff check src/ echo "✓ Lint检查通过" # 类型检查 mypy src/ echo "✓ 类型检查通过" # 测试 pytest tests/ -v echo "✓ 测试通过" echo "所有检查通过!"
6. 总结
代码质量保障要点:
- 自动化:使用pre-commit和CI/CD自动化检查
- 覆盖率:保持80%+的测试覆盖率
- 持续改进:定期审视和改进代码质量
到此这篇关于Python代码质量之从规范到自动化检查全过程的文章就介绍到这了,更多相关Python代码质量内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
