PuzzlesUpdataNotes
RNA-Puzzles 代码优化历史
优化原则
- 代码现代化
- 使用 Python 3 特性
- 添加类型提示
- 使用数据类
- 使用 f-strings 替代 % 格式化
- 使用 pathlib 处理路径
- 代码结构优化
- 引入配置类管理常量
- 使用类封装相关功能
- 模块化设计
- 清晰的职责划分
- 错误处理增强
- 添加异常处理
- 提供详细的错误信息
- 优雅的错误恢复
- 代码可维护性
- 添加详细的文档字符串
- 统一的代码风格
- 清晰的命名规范
- 移除冗余代码
- 向后兼容性
- 保留原有接口
- 提供兼容层
- 平滑过渡
文件修改历史
1. msgs.py
主要改进
- 引入
LogLevel
枚举管理日志级别 - 创建
MessageHandler
类封装消息处理 - 添加类型提示和文档字符串
- 改进错误处理
- 提供便捷方法
代码对比
旧代码:
def show(prefix, message, new_line=True, back=False):
if back:
sys.stderr.write("\b" * 50)
sys.stderr.write("%-10s >> %s%s" % (prefix, message, "\n" if new_line else ""))
if back:
sys.stderr.flush()
if prefix == "FATAL":
sys.stderr.write("Abrupt termination!\n")
quit()
新代码:
class MessageHandler:
def __init__(self):
self._min_level = LogLevel.INFO
self._max_prefix_length = 10
def show(self,
prefix: str,
message: str,
new_line: bool = True,
back: bool = False,
level: Optional[LogLevel] = None) -> None:
"""显示消息
Args:
prefix: 消息前缀
message: 消息内容
new_line: 是否换行
back: 是否回退
level: 日志级别,如果为None则从prefix推断
"""
# 确定日志级别
if level is None:
try:
level = LogLevel(prefix.upper())
except ValueError:
level = LogLevel.INFO
# 检查是否达到最小日志级别
if level.value < self._min_level.value:
return
# 格式化消息
new_line_txt = "\n" if new_line else ""
back_txt = "\b" * 50 if back else ""
prefix = prefix[:self._max_prefix_length]
# 输出消息
sys.stderr.write(
f"{back_txt}{prefix:<{self._max_prefix_length}} >> {message}{new_line_txt}"
)
2. utils.py
主要改进
- 引入
UtilsConfig
类管理配置 - 创建
FileHandler
类处理文件操作 - 使用数据类优化
Result
和Eval
类 - 添加类型提示和错误处理
- 优化文件操作使用
with
语句
代码对比
旧代码:
def MakeDIR(DIR):
if not os.path.isdir(DIR):
os.makedirs(DIR)
else:
print('Directory "%s" already exists' % DIR)
def CleanFormat(f):
os.system("mac2unix -q %s" % f)
os.system("dos2unix -q %s" % f)
新代码:
class FileHandler:
@staticmethod
def make_dir(directory: str) -> None:
"""创建目录,如果已存在则提示"""
if not os.path.isdir(directory):
os.makedirs(directory)
else:
print(f'Directory "{directory}" already exists')
@staticmethod
def clean_format(file_path: str) -> None:
"""清理文件格式"""
os.system(f"mac2unix -q {file_path}")
os.system(f"dos2unix -q {file_path}")
3. pdb_utils.py
主要改进
- 引入
PDBConfig
类管理配置 - 优化
PDBNormalizer
类的错误处理 - 使用数据类优化
Residue
类 - 改进
PDBStruct
类的结构和方法 - 增强
PDBComparer
类的功能
代码对比
旧代码:
class Residue:
def __init__(self, chain, pos, nt, res):
self.chain = chain
self.pos = pos
self.nt = nt
self.res = res
新代码:
@dataclass
class Residue:
"""残基类"""
chain: str
pos: int
nt: str
res: Any # Bio.PDB.Residue类型
def key(self) -> str:
"""生成残基键值"""
return f"{self.chain}:{self.pos}"
def __str__(self) -> str:
"""字符串表示"""
return f"{self.chain}:{self.pos}:{self.nt} > {self.res}"
4. extract.py
主要改进
- 添加模块级文档字符串
- 引入
ResidueRange
数据类 - 优化
ResidueSelector
类 - 改进错误处理
- 使用 f-strings 和类型提示
代码对比
旧代码:
def extract_PDB(input_file, res_list_str, output_file):
parser = PDBParser()
structure = parser.get_structure("SI", input_file)
selector = MySelect()
selector.config(res_list_str)
io = PDBIO()
io.set_structure(structure)
io.save(output_file, selector)
新代码:
def extract_pdb(input_file: str, res_list_str: str, output_file: str) -> None:
"""从PDB文件中提取残基
Args:
input_file: 输入PDB文件
res_list_str: 残基列表字符串
output_file: 输出PDB文件
"""
try:
# 解析输入结构
parser = PDBParser()
structure = parser.get_structure("SI", input_file)
# 准备选择器
selector = ResidueSelector()
res_list = parse_res_list(res_list_str)
selector.config(res_list)
# 写入输出文件
write_pdb(structure, selector, output_file)
except Exception as e:
msgs.fatal(f"Error processing PDB file: {str(e)}")
5. rnapuzzles_assess.py
主要改进
- 引入
RNAPuzzleConfig
类管理配置 - 创建
RNAPuzzleAssessor
类封装评估逻辑 - 优化文件路径处理
- 改进错误处理和日志记录
- 使用类型提示和文档字符串
代码对比
旧代码:
# 全局配置
PUZZLE_NAME = sys.argv[1]
if len(sys.argv)>2:
PVALUE = sys.argv[2]
#settings
molprobity_bin_dir='/home/chichau/Software/MolProbity/cmdline'
WORK_DIR = '/home/chichau/RNA-Puzzles'
DP_SCRIPT = "%s/bin/DPS_1.0.2/dp.py"%WORK_DIR
CONFIG_DIR = "%s/bin/config" %WORK_DIR
RESIDUES_LIST = "%s/residues.list" %CONFIG_DIR
ATOMS_LIST = "%s/atoms.list" %CONFIG_DIR
PUZZLE_DIR = "%s/Puzzles/%s" %(WORK_DIR, PUZZLE_NAME)
SOLUTION_DIR = '%s/Puzzles/solution/%s'%(WORK_DIR, PUZZLE_NAME)
# ... 更多全局变量 ...
def MakeDirectory():
dirs = [PUZZLE_DIR, SOLUTION_DIR, ORIGINAL_DIR, INDEX_DIR, MOLPROBITY_DIR, \
STEP1_DIR, STEP2_DIR, STEP3_DIR,STEP4_DIR,STEP5_DIR, DATA_DIR, '%s/pdb'%DATA_DIR, '%s/dp'%DATA_DIR,]
for d in dirs:
utils.MakeDIR(d)
新代码:
class RNAPuzzleConfig:
"""RNA Puzzle评估配置类"""
def __init__(self, puzzle_name, work_dir, pvalue=None):
self.puzzle_name = puzzle_name
self.work_dir = work_dir
self.pvalue = pvalue
self.setup_paths()
def setup_paths(self):
"""设置所有路径配置"""
# MolProbity配置
self.molprobity_bin_dir = '/home/chichau/Software/MolProbity/cmdline'
# 基础路径配置
self.dp_script = f"{self.work_dir}/bin/DPS_1.0.2/dp.py"
self.config_dir = f"{self.work_dir}/bin/config"
self.residues_list = f"{self.config_dir}/residues.list"
self.atoms_list = f"{self.config_dir}/atoms.list"
# 主要目录配置
self.puzzle_dir = f"{self.work_dir}/Puzzles/{self.puzzle_name}"
self.solution_dir = f"{self.work_dir}/Puzzles/solution/{self.puzzle_name}"
self.original_dir = f"{self.work_dir}/Puzzles/original/{self.puzzle_name}"
self.index_dir = f"{self.puzzle_dir}/index"
# 步骤目录配置
self.step1_dir = f"{self.puzzle_dir}/step1"
self.step2_dir = f"{self.puzzle_dir}/step2"
self.step3_dir = f"{self.puzzle_dir}/step3"
self.step4_dir = f"{self.puzzle_dir}/step4"
self.step5_dir = f"{self.puzzle_dir}/step5"
self.molprobity_dir = f"{self.puzzle_dir}/molprobity"
# 输出文件配置
self.evals_file = f"{self.puzzle_dir}/evals.txt"
self.table_file = f"{self.work_dir}/Puzzles/table/{self.puzzle_name}.txt"
# Markdown和数据目录配置
self.markdown_file = f"/home/chichau/rnapuzzles/source/_posts/2000-01-01-{self.puzzle_name}-3d.markdown"
self.data_dir = f"/home/chichau/rnapuzzles/source/data/{self.puzzle_name}"
旧代码:
def NormalizeSolutions():
sys.stderr.write("Step1: Normalize solution file!\n")
os.chdir(SOLUTION_DIR)
SPLIT_FILE = 'split.txt'
if os.path.isfile( SPLIT_FILE ) :
splits = open( SPLIT_FILE ).read().strip().split( "\n" )
for (i, line) in enumerate(splits):
xx=line.split('\t')
SOLUTION_NAME='%s.pdb'%xx[0]
SOLUTION_NORMAL = '%s_solution_%s.pdb'%(PUZZLE_NAME,i)
os.system( "cp %s %s" %(SOLUTION_NAME, SOLUTION_NORMAL) )
utils.CleanFormat(SOLUTION_NORMAL)
pdb_normalizer = pdb_utils.PDBNormalizer( RESIDUES_LIST, ATOMS_LIST )
ok = pdb_normalizer.parse( SOLUTION_NORMAL, SOLUTION_NORMAL )
if not ok :sys.stderr.write("ERROR: solution file not normalized!\n")
if len(xx)>1:
coords=xx[1]
extract.extract_PDB(SOLUTION_NORMAL,coords,'%s_extract_%d.pdb'%(PUZZLE_NAME, i))
open('%s_solution_%d.index'%(PUZZLE_NAME, i),'w').write(coords)
sys.stderr.write("INFO: Solution %d extracted\n" %i )
else:
os.system( "cp %s %s_extract_%d.pdb" %(SOLUTION_NORMAL, PUZZLE_NAME,i) )
else:sys.stderr.write('ERROR split.txt not found!')
新代码:
def normalize_solutions(self):
"""标准化解决方案文件"""
sys.stderr.write("Step1: Normalize solution file!\n")
os.chdir(self.config.solution_dir)
SPLIT_FILE = 'split.txt'
if os.path.isfile(SPLIT_FILE):
splits = open(SPLIT_FILE).read().strip().split("\n")
for (i, line) in enumerate(splits):
xx = line.split('\t')
SOLUTION_NAME = f'{xx[0]}.pdb'
SOLUTION_NORMAL = f'{self.config.puzzle_name}_solution_{i}.pdb'
os.system(f"cp {SOLUTION_NAME} {SOLUTION_NORMAL}")
utils.CleanFormat(SOLUTION_NORMAL)
ok = self.pdb_normalizer.parse(SOLUTION_NORMAL, SOLUTION_NORMAL)
if not ok:
sys.stderr.write("ERROR: solution file not normalized!\n")
if len(xx) > 1:
coords = xx[1]
extract.extract_PDB(SOLUTION_NORMAL, coords, f'{self.config.puzzle_name}_extract_{i}.pdb')
open(f'{self.config.puzzle_name}_solution_{i}.index', 'w').write(coords)
sys.stderr.write(f"INFO: Solution {i} extracted\n")
else:
os.system(f"cp {SOLUTION_NORMAL} {self.config.puzzle_name}_extract_{i}.pdb")
else:
sys.stderr.write('ERROR split.txt not found!')
旧代码:
if __name__ == '__main__':
if(len(sys.argv)<3):
MakeDirectory()
else:
MakeDirectory()
NormalizeSolutions()
NormalizePrediction()
Measure()
新代码:
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: python rnapuzzles_assess.py <puzzle_name> <work_dir> [pvalue]")
sys.exit(1)
# 创建配置对象
config = RNAPuzzleConfig(sys.argv[1], sys.argv[2])
if len(sys.argv) > 3:
config.pvalue = sys.argv[3]
# 创建评估器并执行评估
assessor = RNAPuzzleAssessor(config)
assessor.assess()
主要改进点
- 配置管理
- 使用
RNAPuzzleConfig
类集中管理所有配置 - 使用 f-strings 替代 % 格式化
- 路径配置更加清晰和模块化
- 使用
- 类封装
- 创建
RNAPuzzleAssessor
类封装评估逻辑 - 将全局函数转换为类方法
- 减少全局变量的使用
- 创建
- 错误处理
- 添加参数验证
- 改进错误消息
- 使用异常处理
- 代码组织
- 更清晰的方法命名
- 添加文档字符串
- 改进代码结构
- 类型提示
- 添加参数类型注解
- 添加返回值类型注解
- 提高代码可读性
使用示例
1. 消息处理
旧代码:
show("INFO", "Processing file...")
show("ERROR", "File not found", new_line=True)
show("FATAL", "Critical error", back=True)
新代码:
# 使用便捷方法
msgs.info("Processing file...")
msgs.error("File not found")
msgs.fatal("Critical error")
# 使用原始show函数(向后兼容)
show("INFO", "Processing file...")
2. 文件操作
旧代码:
MakeDIR("output")
CleanFormat("input.txt")
command("ls -l")
新代码:
# 使用FileHandler类
FileHandler.make_dir("output")
FileHandler.clean_format("input.txt")
FileHandler.execute_command("ls -l")
# 使用兼容函数
MakeDIR("output")
CleanFormat("input.txt")
command("ls -l")
3. PDB处理
旧代码:
res = Residue("A", 1, "G", residue)
print(res.chain, res.pos)
新代码:
res = Residue("A", 1, "G", residue)
print(res.key()) # 输出: "A:1"
print(str(res)) # 输出: "A:1:G > <residue>"
注意事项
- 向后兼容性
- 所有修改都保持了原有接口
- 提供了兼容层确保现有代码继续工作
- 建议逐步迁移到新的API
- 错误处理
- 新增了详细的错误信息
- 使用异常处理替代直接退出
- 提供了错误恢复机制
- 性能优化
- 使用更高效的数据结构
- 优化了文件操作
- 减少了重复计算
- 代码维护
- 添加了详细的文档
- 统一了代码风格
- 提高了代码可读性
后续优化建议
- 测试覆盖
- 添加单元测试
- 添加集成测试
- 添加性能测试
- 文档完善
- 添加API文档
- 添加使用示例
- 添加性能基准
- 功能增强
- 添加更多配置选项
- 优化性能瓶颈
- 增加新特性
- 代码质量
- 添加代码检查
- 添加类型检查
- 优化代码结构
Python 2 到 Python 3 的升级说明
1. 字符串处理
旧代码 (Python 2):
# 字符串格式化
name = "RNA"
print "Processing %s..." % name
print "File %s not found" % filename
# 字符串编码
text = u"RNA结构"
encoded = text.encode('utf-8')
decoded = encoded.decode('utf-8')
# 字符串比较
if cmp(str1, str2) == 0:
print "Strings are equal"
新代码 (Python 3):
# 字符串格式化
name = "RNA"
print(f"Processing {name}...") # f-strings
print("File {} not found".format(filename)) # str.format()
# 字符串编码 (Python 3默认使用Unicode)
text = "RNA结构" # 默认Unicode
encoded = text.encode('utf-8')
decoded = encoded.decode('utf-8')
# 字符串比较
if str1 == str2: # 直接比较
print("Strings are equal")
2. 除法运算
旧代码 (Python 2):
# 整数除法
result = 5 / 2 # 结果为2
# 浮点数除法
result = 5.0 / 2 # 结果为2.5
新代码 (Python 3):
# 整数除法
result = 5 // 2 # 结果为2
# 浮点数除法
result = 5 / 2 # 结果为2.5
3. 输入输出
旧代码 (Python 2):
# 输入
user_input = raw_input("Enter value: ")
# 输出
print "Processing..." # 语句
print >> sys.stderr, "Error message" # 重定向输出
新代码 (Python 3):
# 输入
user_input = input("Enter value: ")
# 输出
print("Processing...") # 函数
print("Error message", file=sys.stderr) # 重定向输出
4. 迭代器和生成器
旧代码 (Python 2):
# 迭代器
class Iterator:
def next(self):
return self.data.next()
# 生成器
def generator():
yield 1
yield 2
新代码 (Python 3):
# 迭代器
class Iterator:
def __next__(self):
return next(self.data)
# 生成器 (语法基本相同,但行为更一致)
def generator():
yield 1
yield 2
5. 异常处理
旧代码 (Python 2):
try:
result = process_data()
except Exception, e: # 旧语法
print "Error:", str(e)
新代码 (Python 3):
try:
result = process_data()
except Exception as e: # 新语法
print("Error:", str(e))
6. 类型提示
旧代码 (Python 2):
def process_data(data):
# 没有类型提示
return data
新代码 (Python 3):
from typing import List, Optional
def process_data(data: List[str]) -> Optional[str]:
# 添加类型提示
return data[0] if data else None
7. 数据类
旧代码 (Python 2):
class Residue:
def __init__(self, chain, pos, nt, res):
self.chain = chain
self.pos = pos
self.nt = nt
self.res = res
def __repr__(self):
return f"Residue(chain='{self.chain}', pos={self.pos}, nt='{self.nt}')"
新代码 (Python 3):
from dataclasses import dataclass
@dataclass
class Residue:
chain: str
pos: int
nt: str
res: Any
8. 路径处理
旧代码 (Python 2):
import os
path = os.path.join("data", "input.txt")
if os.path.exists(path):
with open(path) as f:
data = f.read()
新代码 (Python 3):
from pathlib import Path
path = Path("data") / "input.txt"
if path.exists():
data = path.read_text()
9. 字典方法
旧代码 (Python 2):
# 字典视图
keys = d.keys()
values = d.values()
items = d.items()
# 字典更新
d.update(e) # e是另一个字典
新代码 (Python 3):
# 字典视图 (返回视图对象而不是列表)
keys = d.keys()
values = d.values()
items = d.items()
# 字典更新 (支持更多格式)
d.update(e) # e可以是字典、键值对列表等
10. 函数注解
旧代码 (Python 2):
def process_data(data):
# 没有函数注解
return data
新代码 (Python 3):
def process_data(data: List[str]) -> Optional[str]:
"""处理数据
Args:
data: 输入数据列表
Returns:
处理后的数据或None
"""
return data[0] if data else None
升级注意事项
- 字符串处理
- Python 3 默认使用 Unicode
- 使用 f-strings 或 str.format() 替代 % 格式化
- 字符串比较更严格
- 除法运算
- 使用 // 进行整数除法
- / 运算符总是返回浮点数
- 输入输出
- raw_input() 改为 input()
- print 成为函数
- 文件操作默认使用文本模式
- 异常处理
- 使用 as 关键字捕获异常
- 异常链更清晰
- 类型系统
- 添加类型提示
- 使用数据类简化类定义
- 更严格的类型检查
- 标准库变化
- pathlib 替代 os.path
- 字典视图返回视图对象
- 更多内置函数
- 性能优化
- 迭代器更高效
- 字符串处理更快
- 内存使用更优化