- name
- Python分析器
- description
- 当进行Python代码审查、性能优化、类型安全检查或测试策略规划时,分析Python代码质量和最佳实践。
- license
- MIT
# Python分析器技能
## 概述
Python让编写糟糕代码变得容易。分析代码质量以防止技术债务。
**核心原则**: Python让编写糟糕代码变得容易。分析代码质量以防止技术债务。
## 何时使用
**始终:**
- Python代码审查
- 性能优化
- 类型安全检查
- 测试策略规划
- 代码重构
- 架构设计评审
**触发短语:**
- "分析Python代码"
- "Python性能优化"
- "代码质量检查"
- "Python最佳实践"
- "类型安全分析"
- "测试覆盖率"
## Python分析功能
### 代码质量
- PEP 8规范检查
- 代码复杂度分析
- 代码重复检测
- 命名规范检查
- 文档字符串审查
### 性能分析
- 瓶颈识别
- 内存使用分析
- 算法复杂度评估
- 并发性能检查
- I/O优化建议
### 类型安全
- 类型注解检查
- 类型推断分析
- 类型错误检测
- mypy兼容性
- 运行时类型验证
## 常见Python问题
### 代码风格问题
```
问题:
不符合PEP 8编码规范
错误示例:
- 变量名使用驼峰命名
- 行长度超过79字符
- 缺少空行分隔
- 导入语句不规范
解决方案:
1. 使用black自动格式化
2. 配置flake8检查
3. 使用isort整理导入
4. 遵循PEP 8指南
```
### 性能问题
```
问题:
Python代码性能低下
错误示例:
- 在循环中使用+拼接字符串
- 不必要的列表推导
- 全局变量访问
- 缺少缓存机制
解决方案:
1. 使用join拼接字符串
2. 优化数据结构选择
3. 使用局部变量
4. 实现缓存策略
```
### 类型安全问题
```
问题:
缺少类型注解导致运行时错误
错误示例:
- 函数参数无类型提示
- 返回值类型不明确
- 可选参数未标注
- 泛型使用不当
解决方案:
1. 添加类型注解
2. 使用Optional标注可选值
3. 使用TypeVar定义泛型
4. 运行mypy检查
```
## 代码实现示例
### Python代码分析器
```python
import ast
import os
import re
from collections import defaultdict
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from pathlib import Path
@dataclass
class CodeIssue:
"""代码问题"""
file_path: str
line_number: int
column: int
severity: str # error, warning, info
message: str
rule_id: str
suggestion: Optional[str] = None
@dataclass
class FunctionMetrics:
"""函数指标"""
name: str
line_start: int
line_end: int
complexity: int
arguments: int
returns: int
docstring: bool
type_annotations: bool
@dataclass
class ClassMetrics:
"""类指标"""
name: str
line_start: int
line_end: int
methods: int
attributes: int
inheritance_depth: int
docstring: bool
class PythonAnalyzer:
def __init__(self, config: Optional[Dict] = None):
self.config = config or self.get_default_config()
self.issues: List[CodeIssue] = []
self.function_metrics: List[FunctionMetrics] = []
self.class_metrics: List[ClassMetrics] = []
self.imports: Dict[str, List[str]] = defaultdict(list)
self.complexity_threshold = self.config.get('complexity_threshold', 10)
def get_default_config(self) -> Dict:
"""获取默认配置"""
return {
'max_line_length': 79,
'max_function_length': 50,
'max_class_length': 200,
'complexity_threshold': 10,
'require_type_annotations': True,
'require_docstrings': True,
'check_imports': True,
'check_naming': True
}
def analyze_file(self, file_path: str) -> Dict[str, Any]:
"""分析单个Python文件"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 解析AST
tree = ast.parse(content, filename=file_path)
# 重置状态
self.issues = []
self.function_metrics = []
self.class_metrics = []
self.imports = defaultdict(list)
# 执行各种分析
self.check_style(file_path, content)
self.analyze_ast(tree, file_path)
self.check_complexity(tree, file_path)
self.check_type_safety(tree, file_path)
self.check_documentation(tree, file_path)
return {
'file_path': file_path,
'issues': self.issues,
'function_metrics': self.function_metrics,
'class_metrics': self.class_metrics,
'imports': dict(self.imports),
'summary': self.generate_summary()
}
except SyntaxError as e:
return {
'file_path': file_path,
'error': f'语法错误: {e}',
'issues': [CodeIssue(
file_path=file_path,
line_number=e.lineno or 0,
column=e.offset or 0,
severity='error',
message=f'语法错误: {e.msg}',
rule_id='syntax_error'
)]
}
except Exception as e:
return {
'file_path': file_path,
'error': f'分析失败: {e}',
'issues': []
}
def analyze_directory(self, directory: str) -> Dict[str, Any]:
"""分析整个目录"""
results = []
all_issues = []
for root, dirs, files in os.walk(directory):
# 跳过虚拟环境和缓存目录
dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['__pycache__', 'venv', 'env']]
for file in files:
if file.endswith('.py'):
file_path = os.path.join(root, file)
result = self.analyze_file(file_path)
results.append(result)
all_issues.extend(result.get('issues', []))
return {
'directory': directory,
'files': results,
'total_issues': len(all_issues),
'issue_summary': self.categorize_issues(all_issues),
'recommendations': self.generate_recommendations(all_issues)
}
def check_style(self, file_path: str, content: str) -> None:
"""检查代码风格"""
lines = content.split('\n')
for line_num, line in enumerate(lines, 1):
# 检查行长度
if len(line) > self.config['max_line_length']:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=line_num,
column=self.config['max_line_length'],
severity='warning',
message=f'行长度超过{self.config["max_line_length"]}字符',
rule_id='line_too_long',
suggestion='考虑拆分长行或使用括号换行'
))
# 检查尾随空格
if line.endswith(' '):
self.issues.append(CodeIssue(
file_path=file_path,
line_number=line_num,
column=len(line.rstrip()),
severity='info',
message='行末有多余空格',
rule_id='trailing_whitespace',
suggestion='删除行末空格'
))
# 检查Tab字符
if '\t' in line:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=line_num,
column=line.find('\t'),
severity='warning',
message='使用了Tab字符,应该使用空格',
rule_id='tab_character',
suggestion='将Tab替换为4个空格'
))
def analyze_ast(self, tree: ast.AST, file_path: str) -> None:
"""分析AST结构"""
# 分析导入
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
self.imports['import'].append(alias.name)
elif isinstance(node, ast.ImportFrom):
module = node.module or ''
for alias in node.names:
self.imports['from'].append(f'{module}.{alias.name}')
# 分析函数和类
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
self.analyze_function(node, file_path)
elif isinstance(node, ast.ClassDef):
self.analyze_class(node, file_path)
def analyze_function(self, node: ast.FunctionDef, file_path: str) -> None:
"""分析函数"""
# 计算复杂度
complexity = self.calculate_complexity(node)
# 统计参数
args = len(node.args.args) + len(node.args.kwonlyargs)
if node.args.vararg:
args += 1
if node.args.kwarg:
args += 1
# 检查返回语句
returns = len([n for n in ast.walk(node) if isinstance(n, ast.Return)])
# 检查文档字符串
has_docstring = (ast.get_docstring(node) is not None)
# 检查类型注解
has_type_annotations = (
all(arg.annotation for arg in node.args.args) and
(node.returns is not None)
)
metrics = FunctionMetrics(
name=node.name,
line_start=node.lineno,
line_end=node.end_lineno or node.lineno,
complexity=complexity,
arguments=args,
returns=returns,
docstring=has_docstring,
type_annotations=has_type_annotations
)
self.function_metrics.append(metrics)
# 检查问题
if complexity > self.complexity_threshold:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
column=0,
severity='warning',
message=f'函数{node.name}复杂度过高({complexity})',
rule_id='high_complexity',
suggestion='考虑拆分函数或简化逻辑'
))
if not has_docstring and self.config['require_docstrings']:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
column=0,
severity='info',
message=f'函数{node.name}缺少文档字符串',
rule_id='missing_docstring',
suggestion='添加函数文档说明'
))
if not has_type_annotations and self.config['require_type_annotations']:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
column=0,
severity='info',
message=f'函数{node.name}缺少类型注解',
rule_id='missing_type_annotations',
suggestion='添加参数和返回值类型注解'
))
def analyze_class(self, node: ast.ClassDef, file_path: str) -> None:
"""分析类"""
# 统计方法和属性
methods = 0
attributes = 0
for item in node.body:
if isinstance(item, ast.FunctionDef):
methods += 1
elif isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name):
attributes += 1
# 计算继承深度
inheritance_depth = self.calculate_inheritance_depth(node)
# 检查文档字符串
has_docstring = (ast.get_docstring(node) is not None)
metrics = ClassMetrics(
name=node.name,
line_start=node.lineno,
line_end=node.end_lineno or node.lineno,
methods=methods,
attributes=attributes,
inheritance_depth=inheritance_depth,
docstring=has_docstring
)
self.class_metrics.append(metrics)
# 检查问题
if inheritance_depth > 3:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
column=0,
severity='warning',
message=f'类{node.name}继承层次过深({inheritance_depth})',
rule_id='deep_inheritance',
suggestion='考虑使用组合替代继承'
))
if not has_docstring and self.config['require_docstrings']:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
column=0,
severity='info',
message=f'类{node.name}缺少文档字符串',
rule_id='missing_docstring',
suggestion='添加类文档说明'
))
def calculate_complexity(self, node: ast.AST) -> int:
"""计算圈复杂度"""
complexity = 1 # 基础复杂度
for child in ast.walk(node):
if isinstance(child, (ast.If, ast.While, ast.For, ast.AsyncFor)):
complexity += 1
elif isinstance(child, ast.ExceptHandler):
complexity += 1
elif isinstance(child, ast.With, ast.AsyncWith):
complexity += 1
elif isinstance(child, ast.BoolOp):
complexity += len(child.values) - 1
return complexity
def calculate_inheritance_depth(self, node: ast.ClassDef) -> int:
"""计算继承深度"""
if not node.bases:
return 0
max_depth = 0
for base in node.bases:
if isinstance(base, ast.Name):
# 简化实现,实际需要解析整个模块
max_depth = max(max_depth, 1)
return max_depth
def check_complexity(self, tree: ast.AST, file_path: str) -> None:
"""检查复杂度相关问题"""
# 检查嵌套深度
for node in ast.walk(tree):
if isinstance(node, (ast.For, ast.While, ast.If)):
depth = self.calculate_nesting_depth(node)
if depth > 3:
self.issues.append(CodeIssue(
file_path=file_path,
line_number=node.lineno,
Ver en GitHub