如何使用 Python re.compile 和 REGEX 清理姓名的不同写法?
建议用多个 re.compile 模式分步处理:先删除昵称和旧名,再把姓名与逗号后的附加内容分开,保留姓名部分的首尾两个词,并用白名单识别后缀。
处理时采用两条规则:
- 删除昵称和旧名后,把第一个词视为名字;如果还有其他词,把最后一个词视为姓氏。
- 逗号后的完整内容只有符合后缀白名单才会保留,例如
Jr.、Sr.、III,其余内容全部丢弃。
这两条规则能处理题目中的示例。真实姓名中的复姓、多词名字等情况,还需要额外规则或结构化数据才能准确处理。
原模式为什么会把姓氏捕获为中间名?
原模式的中间名部分使用了贪婪重复:
(?P<middle>(\s[a-zA-Z.'"\-\(\)]+)*)?
它会尽可能多地匹配名字后面的词。由于后面的 last 分组是可选的,即使 middle 连姓氏也匹配进去,整个模式仍能匹配成功。正则引擎因此不必回溯,把最后一个词交给 last。
后缀部分也有类似问题:
(?P<suffix>([a-zA-Z'\-]+)*)?
.*
Duke 符合字母字符类,后面的 .* 又能匹配剩余的 of CatTown,所以 Duke 会被误判为后缀。
改成非贪婪匹配也不能解决所有问题。姓名后缀和贵族头衔都由字母组成,又出现在同一位置,需要明确的规则来区分。
分步清理的实现
后缀白名单可按数据范围调整,代码中的名单仅作示例。
import re
# 双引号中的昵称,例如 "Lady Gaga"
NICKNAME_RE = re.compile(r'"[^"]*"')
# 非嵌套括号中的旧名,要求右括号前是连字符,例如 (Baldwin-)
OLD_NAME_RE = re.compile(r'\([^()]*-\)')
# 姓名与后缀或头衔之间的分隔符
SUFFIX_SEPARATOR_RE = re.compile(r',\s+')
# 删除后缀中的标点和符号,保留字母、数字及空白
SUFFIX_PUNCT_RE = re.compile(r'[^\w\s]|_')
# 键用于匹配,值用于统一输出格式
SUFFIXES = {
"jr": "Jr",
"sr": "Sr",
"ii": "II",
"iii": "III",
"iv": "IV",
"v": "V",
}
def clean_name(name: str) -> str:
# 用空格替换,避免删除内容后把相邻姓名词拼接起来
text = NICKNAME_RE.sub(" ", name)
text = OLD_NAME_RE.sub(" ", text)
# 先移除带分隔符的附加内容,再提取名字和姓氏
parts = SUFFIX_SEPARATOR_RE.split(text.strip(), maxsplit=1)
words = parts[0].split()
if not words:
return ""
result = [words[0]]
if len(words) > 1:
result.append(words[-1])
if len(parts) == 2:
key = SUFFIX_PUNCT_RE.sub("", parts[1]).strip().casefold()
suffix = SUFFIXES.get(key)
if suffix:
result.append(suffix)
return " ".join(result)
先删除昵称和旧名,再提取姓氏,可以处理这样的输入:
Shania (Irene-) Twain
删除 (Irene-) 后剩下 Shania Twain,保留首尾两个词即可。
再看带昵称和头衔的情况:
Charles "Chaz" Fluffy Wong, Duke of CatTown
删除昵称后,逗号前是 Charles Fluffy Wong,保留 Charles Wong。逗号后的完整内容规范化为 duke of cattown,不在白名单中,因此丢弃。
示例结果
调用方式:
names = [
"Sarah Michelle Gellar",
"Leslie Marie DeGuzman Santorini",
"Beyonce",
'Stefani "Lady Gaga" Germanotta',
"Lebron James, Jr.",
"Hailey Bieber (Baldwin-)",
"Shania (Irene-) Twain",
'Charles "Chaz" Fluffy Wong, Duke of CatTown',
]
for name in names:
print(f"{name} -> {clean_name(name)}")
输出:
Sarah Michelle Gellar -> Sarah Gellar
Leslie Marie DeGuzman Santorini -> Leslie Santorini
Beyonce -> Beyonce
Stefani "Lady Gaga" Germanotta -> Stefani Germanotta
Lebron James, Jr. -> Lebron James Jr
Hailey Bieber (Baldwin-) -> Hailey Bieber
Shania (Irene-) Twain -> Shania Twain
Charles "Chaz" Fluffy Wong, Duke of CatTown -> Charles Wong
为什么使用后缀白名单?
题目中的后缀和贵族头衔使用相同的分隔形式,单靠 ,\s+ 无法区分。白名单要求去除标点并忽略大小写后,整个附加字段都能匹配一个已知后缀。
这样,Jr. 会转成 Jr,Duke of CatTown 会被删除。Jr. of CatTown 也不会仅因开头有 Jr. 就被接受。
白名单也会丢弃尚未收录的合法后缀。需要支持更多后缀时,应扩充 SUFFIXES;放宽为任意字母会再次把头衔识别成后缀。
使用时的边界
- 保留第一个词和最后一个词,是这里采用的数据规则,无法据此判断真实姓名结构。例如,输入只有名字和中间名、没有姓氏时,最后一个词仍会被当作姓氏。
- 多词姓氏会被截短。姓氏包含空格时,需要独立的姓氏字段或额外识别规则;用连字符连接的姓氏会作为一个词保留。
- 昵称模式只处理成对的英文双引号,不处理弯引号、转义引号或嵌套结构。
- 旧名模式只删除非嵌套且以
-)结尾的括号内容,普通括号内容不会自动删除。 - 分隔符严格按
,\s+处理,即逗号后至少有一个空白字符。如果数据也允许James,Jr.,可将分隔模式改为r',\s*'。
后缀集合和输入格式固定时,也可以把这些规则合并进单个正则,但仍需保留后缀白名单和姓名拆分的假设。分步实现更便于调整规则和排查误删。
备注:内容仅供参考。
