基于Python實(shí)現(xiàn)字符串規(guī)范檢查與修復(fù)程序
在Python開發(fā)中,代碼風(fēng)格的統(tǒng)一性對(duì)于項(xiàng)目的可維護(hù)性至關(guān)重要。雖然PEP 8沒有強(qiáng)制規(guī)定字符串使用單引號(hào)還是雙引號(hào),但許多團(tuán)隊(duì)會(huì)選擇其中一種作為編碼規(guī)范。本文介紹一個(gè)智能的Python字符串引號(hào)規(guī)范自動(dòng)修復(fù)程序,它能夠自動(dòng)檢測并修復(fù)代碼中的字符串引號(hào)使用不一致問題。
完整實(shí)現(xiàn)代碼
#!/usr/bin/env python3
"""
Python字符串引號(hào)規(guī)范檢查與修復(fù)工具
自動(dòng)檢查單引號(hào)字符串并建議替換為雙引號(hào)
"""
import ast
import tokenize
import argparse
import os
import sys
import json
from pathlib import Path
from typing import List, Dict, Tuple, Set, Any
import fnmatch
class StringQuoteChecker:
"""字符串引號(hào)檢查器"""
def __init__(self):
self.stats = {
'files_processed': 0,
'total_strings': 0,
'single_quote_strings': 0,
'replaced_strings': 0,
'skipped_strings': 0,
'error_files': 0,
'issues_found': 0
}
self.issues = []
def is_excluded_directory(self, filepath: str, exclude_dirs: List[str]) -> bool:
"""檢查是否在排除目錄中"""
path = Path(filepath)
for exclude_dir in exclude_dirs:
if fnmatch.fnmatch(str(path), exclude_dir) or exclude_dir in path.parts:
return True
return False
def is_whitelisted(self, filepath: str, whitelist: List[str]) -> bool:
"""檢查是否在白名單中"""
if not whitelist:
return False
path = Path(filepath)
for pattern in whitelist:
if fnmatch.fnmatch(str(path), pattern):
return True
return False
def is_docstring(self, filepath: str, line_no: int) -> bool:
"""檢查是否為模塊、類或函數(shù)的docstring"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
tree = ast.parse(content)
# 檢查模塊級(jí)docstring
if (isinstance(tree.body[0], ast.Expr) and
isinstance(tree.body[0].value, ast.Str) and
tree.body[0].lineno == line_no):
return True
# 檢查類和函數(shù)的docstring
for node in ast.walk(tree):
if (isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)) and
node.body and
isinstance(node.body[0], ast.Expr) and
isinstance(node.body[0].value, ast.Str) and
node.body[0].lineno == line_no):
return True
except Exception:
pass
return False
def analyze_file(self, filepath: str) -> List[Dict[str, Any]]:
"""分析單個(gè)文件中的字符串引號(hào)使用"""
issues = []
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# 使用tokenize進(jìn)行分詞分析
f.seek(0)
tokens = list(tokenize.generate_tokens(f.readline))
for token in tokens:
if token.type == tokenize.STRING:
self.stats['total_strings'] += 1
string_value = token.string
# 跳過空字符串
if len(string_value) <= 2:
continue
# 檢查是否為單引號(hào)字符串(排除雙引號(hào)包含單引號(hào)的情況)
if (string_value.startswith("'") and string_value.endswith("'") and
not ('"' in string_value and string_value.count('"') >= 2)):
# 檢查前綴
prefix = ''
if string_value[0] in 'rubf' or string_value.startswith(('fr', 'rf', 'br', 'rb')):
# 提取前綴
quote_start = string_value.find("'")
if quote_start > 0:
prefix = string_value[:quote_start]
# 跳過docstring
if self.is_docstring(filepath, token.start[0]):
self.stats['skipped_strings'] += 1
continue
self.stats['single_quote_strings'] += 1
issues.append({
'file': filepath,
'line': token.start[0],
'column': token.start[1],
'original_string': string_value,
'suggested_string': prefix + '"' + string_value[len(prefix)+1:-1] + '"',
'prefix': prefix,
'content': string_value[len(prefix)+1:-1]
})
except Exception as e:
self.stats['error_files'] += 1
print(f"錯(cuò)誤分析文件 {filepath}: {e}")
return issues
def replace_string_in_file(self, filepath: str, replacements: List[Dict[str, Any]]) -> int:
"""在文件中替換字符串"""
replaced_count = 0
try:
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 按行號(hào)降序排序,避免替換時(shí)影響行號(hào)
replacements.sort(key=lambda x: x['line'], reverse=True)
for replacement in replacements:
line_no = replacement['line'] - 1 # 轉(zhuǎn)換為0-based索引
original = replacement['original_string']
suggested = replacement['suggested_string']
# 獲取當(dāng)前行內(nèi)容
line_content = lines[line_no]
# 替換字符串
new_line = line_content.replace(original, suggested, 1)
if new_line != line_content:
lines[line_no] = new_line
replaced_count += 1
print(f"替換: {original} -> {suggested}")
else:
print(f"警告: 無法替換 {original}")
# 寫回文件
if replaced_count > 0:
with open(filepath, 'w', encoding='utf-8') as f:
f.writelines(lines)
except Exception as e:
print(f"錯(cuò)誤替換文件 {filepath}: {e}")
return replaced_count
def process_directory(self, root_dir: str, exclude_dirs: List[str],
whitelist: List[str], auto_fix: bool = False) -> None:
"""處理目錄中的所有Python文件"""
root_path = Path(root_dir)
for py_file in root_path.rglob("*.py"):
if self.is_excluded_directory(str(py_file), exclude_dirs):
continue
if whitelist and not self.is_whitelisted(str(py_file), whitelist):
continue
self.stats['files_processed'] += 1
print(f"\n分析文件: {py_file}")
issues = self.analyze_file(str(py_file))
if issues:
self.issues.extend(issues)
self.stats['issues_found'] += len(issues)
print(f"發(fā)現(xiàn) {len(issues)} 個(gè)單引號(hào)字符串:")
replacements = []
for issue in issues:
print(f" 行 {issue['line']}: {issue['original_string']}")
if auto_fix:
replacements.append(issue)
else:
# 交互式確認(rèn)
response = input(f"替換為 {issue['suggested_string']}? (y/n/a): ").lower()
if response == 'y':
replacements.append(issue)
elif response == 'a':
auto_fix = True
replacements.append(issue)
# 執(zhí)行替換
if replacements:
replaced = self.replace_string_in_file(str(py_file), replacements)
self.stats['replaced_strings'] += replaced
print(f"成功替換 {replaced} 個(gè)字符串")
def save_stats(self, output_file: str) -> None:
"""保存統(tǒng)計(jì)信息到JSON文件"""
stats_data = {
'summary': self.stats,
'issues': self.issues
}
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(stats_data, f, indent=2, ensure_ascii=False)
print(f"\n統(tǒng)計(jì)信息已保存到: {output_file}")
def main():
parser = argparse.ArgumentParser(description='Python字符串引號(hào)規(guī)范檢查與修復(fù)工具')
parser.add_argument('path', help='要檢查的文件或目錄路徑')
parser.add_argument('--exclude', nargs='*', default=[], help='排除的目錄模式')
parser.add_argument('--whitelist', nargs='*', default=[], help='白名單文件模式')
parser.add_argument('--auto-fix', action='store_true', help='自動(dòng)修復(fù)模式')
parser.add_argument('--output', default='string_quote_stats.json', help='統(tǒng)計(jì)輸出文件')
args = parser.parse_args()
checker = StringQuoteChecker()
if os.path.isfile(args.path):
# 處理單個(gè)文件
if args.whitelist and not checker.is_whitelisted(args.path, args.whitelist):
print("文件不在白名單中")
return
issues = checker.analyze_file(args.path)
if issues:
replacements = []
for issue in issues:
print(f"行 {issue['line']}: {issue['original_string']}")
if args.auto_fix:
replacements.append(issue)
else:
response = input(f"替換為 {issue['suggested_string']}? (y/n): ").lower()
if response == 'y':
replacements.append(issue)
if replacements:
replaced = checker.replace_string_in_file(args.path, replacements)
checker.stats['replaced_strings'] += replaced
print(f"成功替換 {replaced} 個(gè)字符串")
elif os.path.isdir(args.path):
# 處理目錄
checker.process_directory(args.path, args.exclude, args.whitelist, args.auto_fix)
else:
print(f"路徑不存在: {args.path}")
return
# 輸出統(tǒng)計(jì)信息
print("\n" + "="*50)
print("統(tǒng)計(jì)摘要:")
for key, value in checker.stats.items():
print(f" {key}: {value}")
# 保存詳細(xì)統(tǒng)計(jì)
checker.save_stats(args.output)
if __name__ == "__main__":
main()
這個(gè)Python編碼規(guī)范自動(dòng)修復(fù)程序展示了如何結(jié)合多種Python標(biāo)準(zhǔn)庫來構(gòu)建一個(gè)實(shí)用的代碼質(zhì)量工具。通過智能的字符串識(shí)別、安全的替換機(jī)制和詳細(xì)的統(tǒng)計(jì)報(bào)告,它能夠在保證代碼安全的前提下,有效地統(tǒng)一代碼風(fēng)格。該工具的設(shè)計(jì)思路和技術(shù)實(shí)現(xiàn)也可以為其他代碼質(zhì)量工具的開發(fā)提供參考。
程序的模塊化設(shè)計(jì)使得它易于擴(kuò)展,未來可以添加更多的代碼規(guī)范檢查功能,如行長度檢查、導(dǎo)入順序整理等,成為一個(gè)全面的Python代碼質(zhì)量工具套件。
程序架構(gòu)設(shè)計(jì)
1. 核心組件
StringQuoteChecker類是整個(gè)程序的核心,負(fù)責(zé):
- 文件遍歷和過濾
- 字符串語法分析
- 問題檢測和修復(fù)
- 統(tǒng)計(jì)信息收集
2. 技術(shù)棧選擇
- ast庫:用于解析Python抽象語法樹,準(zhǔn)確識(shí)別docstring位置
- tokenize庫:進(jìn)行詞法分析,精確提取字符串token
- argparse庫:提供友好的命令行接口
- pathlib庫:跨平臺(tái)路徑處理
關(guān)鍵技術(shù)實(shí)現(xiàn)
1. 智能字符串識(shí)別
def analyze_file(self, filepath: str) -> List[Dict[str, Any]]:
# 使用tokenize精確識(shí)別字符串
tokens = list(tokenize.generate_tokens(f.readline))
for token in tokens:
if token.type == tokenize.STRING:
# 處理帶前綴的字符串:r、u、f、b等
prefix = ''
if string_value[0] in 'rubf' or string_value.startswith(('fr', 'rf', 'br', 'rb')):
quote_start = string_value.find("'")
if quote_start > 0:
prefix = string_value[:quote_start]
2. Docstring智能排除
def is_docstring(self, filepath: str, line_no: int) -> bool:
# 使用AST分析識(shí)別模塊、類、函數(shù)的docstring
tree = ast.parse(content)
# 檢查模塊級(jí)docstring
if (isinstance(tree.body[0], ast.Expr) and
isinstance(tree.body[0].value, ast.Str) and
tree.body[0].lineno == line_no):
return True
3. 安全的文件替換機(jī)制
def replace_string_in_file(self, filepath: str, replacements: List[Dict[str, Any]]) -> int:
# 按行號(hào)降序排序,避免替換時(shí)影響行號(hào)
replacements.sort(key=lambda x: x['line'], reverse=True)
for replacement in replacements:
# 精確替換,避免誤操作
new_line = line_content.replace(original, suggested, 1)
功能特性
1. 智能過濾
- 自動(dòng)忽略docstring
- 支持排除目錄模式匹配
- 提供白名單機(jī)制
- 正確處理字符串前綴
2. 安全修復(fù)
- 交互式確認(rèn)或自動(dòng)修復(fù)模式
- 替換前備份檢查
- 詳細(xì)的替換日志
3. 全面統(tǒng)計(jì)
- JSON格式的詳細(xì)報(bào)告
- 處理進(jìn)度跟蹤
- 錯(cuò)誤處理記錄
測試用例詳細(xì)說明
測試文件示例 (test_example.py)
'''模塊docstring - 應(yīng)該被忽略'''
# 單行單引號(hào)字符串 - 應(yīng)該被替換
single_quote = 'hello world'
# 雙引號(hào)字符串 - 應(yīng)該保持不變
double_quote = "hello world"
# 包含單引號(hào)的雙引號(hào)字符串 - 應(yīng)該保持不變
contains_single = "it's a test"
# 包含雙引號(hào)的單引號(hào)字符串 - 應(yīng)該被替換
contains_double = 'he said "hello"'
# 帶前綴的字符串
raw_string = r'raw string'
unicode_string = u'unicode string'
bytes_string = b'bytes string'
formatted_string = f'formatted {single_quote}'
class MyClass:
'''類docstring - 應(yīng)該被忽略'''
def __init__(self):
'''方法docstring - 應(yīng)該被忽略'''
self.message = 'instance attribute'
def my_function():
'''函數(shù)docstring - 應(yīng)該被忽略'''
local_var = 'local variable'
return 'return value'
測試命令
# 交互式模式 python string_quote_checker.py test_example.py # 自動(dòng)修復(fù)模式 python string_quote_checker.py test_example.py --auto-fix # 目錄處理模式 python string_quote_checker.py ./src --exclude "*/migrations/*" --whitelist "*.py" --output stats.json
預(yù)期輸出結(jié)果
修復(fù)后的test_example.py:
'''模塊docstring - 應(yīng)該被忽略'''
# 單行單引號(hào)字符串 - 應(yīng)該被替換
single_quote = "hello world"
# 雙引號(hào)字符串 - 應(yīng)該保持不變
double_quote = "hello world"
# 包含單引號(hào)的雙引號(hào)字符串 - 應(yīng)該保持不變
contains_single = "it's a test"
# 包含雙引號(hào)的單引號(hào)字符串 - 應(yīng)該被替換
contains_double = "he said \"hello\""
# 帶前綴的字符串
raw_string = r"raw string"
unicode_string = u"unicode string"
bytes_string = b"bytes string"
formatted_string = f"formatted {single_quote}"
class MyClass:
'''類docstring - 應(yīng)該被忽略'''
def __init__(self):
'''方法docstring - 應(yīng)該被忽略'''
self.message = "instance attribute"
def my_function():
'''函數(shù)docstring - 應(yīng)該被忽略'''
local_var = "local variable"
return "return value"
統(tǒng)計(jì)輸出示例 (stats.json)
{
"summary": {
"files_processed": 1,
"total_strings": 15,
"single_quote_strings": 8,
"replaced_strings": 6,
"skipped_strings": 3,
"error_files": 0,
"issues_found": 6
},
"issues": [
{
"file": "test_example.py",
"line": 3,
"column": 16,
"original_string": "'hello world'",
"suggested_string": "\"hello world\"",
"prefix": "",
"content": "hello world"
}
]
}
技術(shù)挑戰(zhàn)與解決方案
1. 字符串前綴處理
挑戰(zhàn):Python支持多種字符串前綴(r, u, f, b等),需要正確識(shí)別和保留。
解決方案:通過分析字符串開始部分,提取前綴并確保在替換時(shí)正確保留。
2. Docstring準(zhǔn)確識(shí)別
挑戰(zhàn):需要區(qū)分普通字符串和docstring。
解決方案:結(jié)合AST分析和行號(hào)定位,精確識(shí)別模塊、類、函數(shù)級(jí)別的docstring。
3. 安全替換機(jī)制
挑戰(zhàn):避免在替換過程中破壞代碼結(jié)構(gòu)。
解決方案:使用精確的字符串替換,按行號(hào)降序處理,避免行號(hào)變化影響。
應(yīng)用場景
代碼規(guī)范統(tǒng)一:在大型項(xiàng)目中統(tǒng)一字符串引號(hào)風(fēng)格
代碼審查:在CI/CD流程中自動(dòng)檢查代碼規(guī)范
遺留代碼遷移:幫助遷移舊代碼到新的編碼標(biāo)準(zhǔn)
教學(xué)工具:幫助學(xué)生理解Python編碼規(guī)范
到此這篇關(guān)于基于Python實(shí)現(xiàn)字符串規(guī)范檢查與修復(fù)程序的文章就介紹到這了,更多相關(guān)Python字符串規(guī)范檢查與修復(fù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python游戲開發(fā)之視頻轉(zhuǎn)彩色字符動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了python游戲開發(fā)之視頻轉(zhuǎn)彩色字符動(dòng)畫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
PyInstaller打包selenium-wire過程中常見問題和解決指南
常用的打包工具 PyInstaller 能將 Python 項(xiàng)目打包成單個(gè)可執(zhí)行文件,但也會(huì)因?yàn)榧嫒菪詥栴}和路徑管理而出現(xiàn)各種運(yùn)行錯(cuò)誤,本指南總結(jié)了打包過程中常見問題和解決方案,大家可以根據(jù)需要進(jìn)行選擇2025-04-04
PyQt5實(shí)現(xiàn)進(jìn)度條與定時(shí)器及子線程同步關(guān)聯(lián)
這篇文章主要為大家詳細(xì)介紹了PyQt5如何實(shí)現(xiàn)進(jìn)度條與定時(shí)器及子線程的同步關(guān)聯(lián),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-01-01
Python基于自然語言處理開發(fā)文本摘要系統(tǒng)
自然語言處理(NLP)是人工智能領(lǐng)域中一個(gè)重要的研究方向,而文本摘要作為NLP的一個(gè)重要應(yīng)用,在信息爆炸的時(shí)代具有重要意義,下面我們來看看如何開發(fā)一個(gè)基于Python的文本摘要系統(tǒng)吧2025-04-04
使用Python-UIAutomation搞定Windows桌面自動(dòng)化的完全指南
還在為重復(fù)點(diǎn)擊Windows應(yīng)用而煩惱嗎?Python-UIAutomation-for-Windows正是你需要的解決方案,本文給大家詳細(xì)介紹了使用Python-UIAutomation搞定Windows桌面自動(dòng)化的完全指南,需要的朋友可以參考下2026-03-03

