109 lines
4.3 KiB
Python
109 lines
4.3 KiB
Python
import libcst as cst
|
|
from libcst import CSTTransformer
|
|
import re
|
|
import os
|
|
from typing import Set, List, Union
|
|
|
|
class GenericRenamerTransformer(CSTTransformer):
|
|
"""
|
|
一个通用的CST转换器,用于安全地将代码中的标识符从多个源名称替换为同一个目标名称,
|
|
并能智能地保留原始名称的大小写风格。
|
|
"""
|
|
def __init__(self, from_names: Union[Set[str], List[str]], to_name: str):
|
|
"""
|
|
Args:
|
|
from_names: 要被替换的源名称集合或列表 (例如 {'t5', 'llama', 'utils'})。
|
|
to_name: 用于替换的目标名称 (例如 'qwen2')。
|
|
"""
|
|
self.to_name = to_name
|
|
|
|
# 1. 构建一个包含所有源名称的正则表达式 | (OR 逻辑)
|
|
# - 使用 re.escape() 确保特殊字符被正确处理。
|
|
# - 使用 | 符号连接所有名称,实现多选一匹配。
|
|
# - 确保列表非空
|
|
if not from_names:
|
|
raise ValueError("from_names 列表不能为空。")
|
|
|
|
escaped_names = [re.escape(name) for name in from_names]
|
|
pattern = "|".join(escaped_names)
|
|
|
|
# 2. 编译一个不区分大小写 (re.IGNORECASE) 的正则表达式
|
|
self.regex = re.compile(pattern, re.IGNORECASE)
|
|
|
|
def _case_preserving_replace(self, match: re.Match) -> str:
|
|
"""
|
|
这是一个自定义的替换函数,它根据匹配到的字符串的大小写风格,
|
|
来决定 to_name 应该使用哪种大小写形式。
|
|
"""
|
|
found_str = match.group(0)
|
|
# 如果找到的是全大写 (e.g., LLAMA)
|
|
if found_str.isupper():
|
|
return self.to_name.upper()
|
|
# 如果找到的是首字母大写 (e.g., Llama)
|
|
if found_str.istitle():
|
|
return self.to_name.title()
|
|
# 默认情况,包括全小写 (e.g., llama),返回全小写
|
|
return self.to_name.lower()
|
|
|
|
def leave_Name(
|
|
self, original_node: cst.Name, updated_node: cst.Name
|
|
) -> cst.Name:
|
|
"""
|
|
当访问离开一个名称节点时,使用正则表达式和自定义替换函数执行重命名。
|
|
"""
|
|
# 使用 regex.sub() 和我们的自定义函数来进行替换
|
|
new_name_str = self.regex.sub(self._case_preserving_replace, updated_node.value)
|
|
|
|
# 仅在名称确实发生改变时才创建一个新节点
|
|
if new_name_str != updated_node.value:
|
|
if not new_name_str.isidentifier():
|
|
original_name = original_node.value
|
|
# 警告,而不是跳过,因为这在依赖于上下文的重命名中可能是允许的。
|
|
# 但对于 cst.Name 节点,它必须是有效标识符。
|
|
print(f"警告:尝试将 '{original_name}' 重命名为无效标识符 '{new_name_str}'。跳过此重命名。")
|
|
return updated_node
|
|
return updated_node.with_changes(value=new_name_str)
|
|
|
|
return updated_node
|
|
|
|
def rename_identifiers(source_code: str, from_names: Union[Set[str], List[str]], to_name: str) -> str:
|
|
"""
|
|
接收一段Python源代码,将其中的所有 from_names 相关标识符安全地重命名为 to_name。
|
|
|
|
Args:
|
|
source_code: 包含Python代码的字符串。
|
|
from_names: 要被替换的源名称集合或列表 (例如 {"t5", "llama"})。
|
|
to_name: 用于替换的目标名称 (例如 "qwen2")。
|
|
|
|
Returns:
|
|
重构后的Python代码字符串。
|
|
"""
|
|
try:
|
|
module = cst.parse_module(source_code)
|
|
transformer = GenericRenamerTransformer(from_names, to_name)
|
|
modified_module = module.visit(transformer)
|
|
return modified_module.code
|
|
except cst.ParserSyntaxError as e:
|
|
print(f"Error: Failed to parse the source code. {e}")
|
|
return source_code
|
|
except ValueError as e:
|
|
print(f"Error in rename process: {e}")
|
|
return source_code
|
|
|
|
# --- 示例用法 ---
|
|
# source_code = """
|
|
# class LlamaModel(T5Model):
|
|
# def forward(self, input_ids):
|
|
# return self.llama_layer(input_ids)
|
|
# LLAMA_CONFIG = 1
|
|
# """
|
|
# from_list = ['llama', 't5']
|
|
# to_name = 'qwen2'
|
|
|
|
# new_code = rename_identifiers(source_code, from_list, to_name)
|
|
# print(new_code)
|
|
# # 预期输出:
|
|
# # class Qwen2Model(Qwen2Model):
|
|
# # def forward(self, input_ids):
|
|
# # return self.qwen2_layer(input_ids)
|
|
# # QWEN2_CONFIG = 1 |