如何在Python中替换一个字符串
如果你正在寻找在 Python 中删除或替换全部或部分字符串的方法,那么本教程就是为你准备的。你将采用一个虚构的聊天室记录,并使用**.replace() 方法和re.sub() 函数 对其进行消毒。
在Python中,.replace() 方法和re.sub() 函数经常被用来通过删除字符串或子串或替换它们来清理文本。在本教程中,你将扮演一家公司的开发人员,该公司通过一对一的文本聊天提供技术支持。你的任务是创建一个脚本,对聊天进行消毒,删除任何个人数据,并将任何脏话替换为表情符号。
你只得到一份很短的聊天记录。
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
尽管这份记录很短,但它是代理一直以来的典型聊天类型。它有用户标识符、ISO时间戳和信息。
在这种情况下,客户johndoe 提出投诉,而公司的政策是对记录稿进行消毒和简化,然后将其传递给独立评估。对信息进行消毒是你的工作!
如何删除或替换一个Python字符串或子串
在Python中替换一个字符串的最基本方法是使用.replace() string方法。
>>>
>>> "Fake Python".replace("Fake", "Real")
'Real Python'
正如你所看到的,你可以将.replace() 链接到任何字符串上,并为该方法提供两个参数。第一个是你想替换的字符串,第二个是替换的内容。
注意:尽管 Python shell 显示了.replace() 的结果,但字符串本身保持不变。你可以通过把你的字符串赋值给一个变量来更清楚地看到这一点。
>>>
>>> name = "Fake Python"
>>> name.replace("Fake", "Real")
'Real Python'
>>> name
'Fake Python'
>>> name = name.replace("Fake", "Real")
'Real Python'
>>> name
'Real Python'
注意,当你简单地调用.replace() 时,name 的值没有变化。但是当你把name.replace() 的结果分配给name 变量时,'Fake Python' 就变成了'Real Python' 。
现在是时候把这些知识应用到成绩单上了。
>>>
>>> transcript = """\
... [support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
... [johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
... [support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
... [johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!"""
>>> transcript.replace("BLASTED", "😤")
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY 😤 ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
将成绩单加载为一个三引号的字符串,然后对其中一个脏话使用.replace() 方法,效果不错。但是还有一个脏话没有被替换,因为在Python中,字符串需要完全匹配。
>>>
>>> "Fake Python".replace("fake", "Real")
'Fake Python'
正如你所看到的,即使一个字母的大小写不匹配,也会阻止任何替换。这意味着,如果你使用.replace() 方法,你需要多次调用它,并进行变化。在这种情况下,你可以在另一个调用.replace() 。
>>>
>>> transcript.replace("BLASTED", "😤").replace("Blast", "😤")
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY 😤 ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : 😤! You're right!
成功了!但你可能在想,对于像通用的转录消毒器这样的东西,这并不是最好的方法。你会想朝着有一个替换列表的方向发展,而不是每次都要打出.replace() 。
设置多个替换规则
你还需要对记录稿进行一些替换,使其成为独立审查可接受的格式。
- 缩短或删除时间戳
- 用代理人和客户替换用户名
现在你开始有更多的字符串需要替换,在.replace() ,会有重复的情况。一个想法是保留一个图元的列表,每个图元有两个项目。这两个项目将对应于你需要传入.replace() 方法的参数--要替换的字符串和替换的字符串。
# transcript_multiple_replace.py
REPLACEMENTS = [
("BLASTED", "😤"),
("Blast", "😤"),
("2022-08-24T", ""),
("+00:00", ""),
("[support_tom]", "Agent "),
("[johndoe]", "Client"),
]
transcript = """
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
"""
for old, new in REPLACEMENTS:
transcript = transcript.replace(old, new)
print(transcript)
在这个版本的脚本中,你创建了一个替换图元的列表,这给了你一个快速添加替换的方法。如果你有大量的替换内容,你甚至可以从一个外部CSV文件中创建这个图元列表。
然后你在替换图元的列表上进行迭代。在每次迭代中,你在字符串上调用.replace() ,用从每个替换元组中解包的old 和new 变量来填充参数。
注意:在这种情况下,for 循环中的解包在功能上与使用索引是一样的。
for replacement in replacements:
new_transcript = new_transcript.replace(replacement[0], replacement[1])
有了这个,你在成绩单的整体可读性上就有了很大的改进。如果你需要的话,也更容易添加替换内容。运行这个脚本可以看到一个更干净的成绩单。
$ python transcript_multiple_replace.py
Agent 10:02:23 : What can I help you with?
Client 10:03:15 : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent 10:03:30 : Are you sure it's not your caps lock?
Client 10:04:03 : 😤! You're right!
这是一份相当干净的成绩单。也许这就是你所需要的一切。但是,如果你内心的自动驾驶者并不高兴,也许是因为还有一些东西可能让你感到不快。
- 如果有另一个使用*-ing的变体或不同的大写字母,如BLAst*,替换脏话就不起作用。
- 从时间戳中删除日期目前只适用于2022年8月24日。
- 移除完整的时间戳将涉及到为每一个可能的时间设置替换对,这不是你所热衷的事情。
- 在Agent后面添加空格,以便将你的列排成一排,但不是很普遍。
如果这些是你关心的问题,那么你可能想把你的注意力转向正则表达式。
利用re.sub() 来制定复杂的规则
每当你想做任何稍微复杂或需要一些通配符的替换时,你通常会想把你的注意力转向正则表达式,也被称为regex。
正则表达式是一种由定义模式的字符组成的迷你语言。这些模式,或称regexes,通常用于在查找和查找及替换操作中搜索字符串。许多编程语言都支持REGEX,而且它被广泛使用。雷格函数甚至会给你带来超能力。
在Python中,利用regex意味着使用re 模块的sub() 函数并建立你自己的regex模式。
# transcript_regex.py
import re
REGEX_REPLACEMENTS = [
(r"blast\w*", "😤"),
(r" [-T:+\d]{25}", ""),
(r"\[support\w*\]", "Agent "),
(r"\[johndoe\]", "Client"),
]
transcript = """
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
"""
for old, new in REGEX_REPLACEMENTS:
transcript = re.sub(old, new, transcript, flags=re.IGNORECASE)
print(transcript)
虽然你可以混合使用sub() 函数和.replace() 方法,但这个例子只使用sub() ,所以你可以看到它是如何使用的。你会注意到,你现在只用一个替换元组就可以替换所有的脏话变化。同样地,你只用一个词组来表示完整的时间戳。
$ python transcript_regex.py
Agent : What can I help you with?
Client : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent : Are you sure it's not your caps lock?
Client : 😤! You're right!
现在你的成绩单已经被完全净化了,所有的噪音都被清除了!这是怎么做到的?这就是重词的魔力。
第一个铰链模式,"blast\w*" ,利用了\w 特殊字符,它将匹配字母数字字符和下划线。在它后面直接添加* 量词,将匹配\w 的零个或多个字符。
第一个模式的另一个重要部分是,re.IGNORECASE 标志使它成为一个不区分大小写的模式。所以现在,任何包含blast 的子串,无论其大小写如何,都将被匹配和替换。
注意: "blast\w*" 模式是相当广泛的,它也会将fibroblast 修改为fibro😤 。它也不能识别该词的礼貌性使用。它只是匹配这些字符。也就是说,你想审查的典型的脏话并没有真正的礼貌性的替代含义!
第二个重码模式使用字符集和量词来替换时间戳。你经常把字符集和量词放在一起使用。例如,一个[abc] 的 regex 模式将匹配a,b, 或c 的一个字符。在它后面直接放一个* 将匹配a,b, 或c 的零个或多个字符。
不过,还有更多的量词。如果你使用[abc]{10} ,它将准确地匹配a,b 或c 的十个字符,而且是以任何顺序和任何组合。还要注意的是,重复的字符是多余的,所以[aa] 相当于[a] 。
对于时间戳,你使用[-T:+\d] 的扩展字符集来匹配所有可能在时间戳中找到的字符。与量词{25} 搭配,这将匹配任何可能的时间戳,至少到10000年为止。
注意:特殊字符,\d ,可以匹配任何数字字符。
时间戳重码模式允许你在时间戳格式中选择任何可能的日期。由于时间对这些记录的独立审查员来说并不重要,所以你用一个空字符串替换它们。可以写一个更高级的铰链,在删除日期的同时保留时间信息。
第三个regex模式是用来选择任何以关键词"support" 开始的用户字符串。注意你要转义(\)方括号([),因为否则这个关键词会被解释为一个字符集。
最后,最后一个regex模式选择了客户端的用户名字符串,并将其替换为"Client" 。
注意:虽然更详细地了解这些regex模式会非常有趣,但本教程并不是关于regex的。另外,你还可以利用奇妙的RegExr网站,因为重合运算是很棘手的,各种级别的重合运算向导都依赖像RegExr这样方便的工具。
RegExr特别好,因为你可以复制和粘贴重码模式,它将为你分解它们并提供解释。
有了regex,你可以大大减少你必须写出来的替换程序的数量。尽管如此,你仍然可能要想出许多模式。由于Rgex并不是最易读的语言,有很多模式很快就会变得难以维护。
值得庆幸的是,有一个巧妙的技巧,re.sub() ,让你对替换的工作方式有更多的控制,而且它提供了一个更可维护的架构。
使用re.sub() 的回调来实现更多的控制
Python 和sub() 的一个技巧是,你可以传入一个回调函数而不是替换字符串。这使你可以完全控制如何进行匹配和替换。
为了开始构建这个版本的誊本消毒脚本,你将使用一个基本的重合码模式来看看如何使用回调函数和sub() 。
# transcript_regex_callback.py
import re
transcript = """
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
"""
def sanitize_message(match):
print(match)
re.sub(r"[-T:+\d]{25}", sanitize_message, transcript)
你所使用的重词模式将与时间戳相匹配,而不是提供一个替换字符串,你将传入一个对sanitize_message() 函数的引用。现在,当sub() 找到一个匹配对象时,它将以匹配对象为参数调用sanitize_message() 。
由于sanitize_message() 只是打印它收到的作为参数的对象,当运行这个时,你会看到匹配对象被打印到控制台。
$ python transcript_regex_callback.py
<re.Match object; span=(15, 40), match='2022-08-24T10:02:23+00:00'>
<re.Match object; span=(79, 104), match='2022-08-24T10:03:15+00:00'>
<re.Match object; span=(159, 184), match='2022-08-24T10:03:30+00:00'>
<re.Match object; span=(235, 260), match='2022-08-24T10:04:03+00:00'>
匹配对象是re 模块的组成部分之一。更基本的re.match() 函数返回一个匹配对象。sub() 不返回任何匹配对象,但在幕后使用它们。
因为你在回调中得到这个匹配对象,你可以使用其中包含的任何信息来构建替换字符串。一旦构建完成,你就返回新的字符串,sub() 将用返回的字符串替换匹配。
将回调应用到脚本中
在你的成绩单消毒脚本中,你将利用匹配对象的.groups() 方法来返回两个捕获组的内容,然后你可以在自己的函数中对每个部分进行消毒,或者丢弃它。
# transcript_regex_callback.py
import re
ENTRY_PATTERN = (
r"\[(.+)\] " # User string, discarding square brackets
r"[-T:+\d]{25} " # Time stamp
r": " # Separator
r"(.+)" # Message
)
BAD_WORDS = ["blast", "dash", "beezlebub"]
CLIENTS = ["johndoe", "janedoe"]
def censor_bad_words(message):
for word in BAD_WORDS:
message = re.sub(rf"{word}\w*", "😤", message, flags=re.IGNORECASE)
return message
def censor_users(user):
if user.startswith("support"):
return "Agent"
elif user in CLIENTS:
return "Client"
else:
raise ValueError(f"unknown client: '{user}'")
def sanitize_message(match):
user, message = match.groups()
return f"{censor_users(user):<6} : {censor_bad_words(message)}"
transcript = """
[support_tom] 2022-08-24T10:02:23+00:00 : What can I help you with?
[johndoe] 2022-08-24T10:03:15+00:00 : I CAN'T CONNECT TO MY BLASTED ACCOUNT
[support_tom] 2022-08-24T10:03:30+00:00 : Are you sure it's not your caps lock?
[johndoe] 2022-08-24T10:04:03+00:00 : Blast! You're right!
"""
print(re.sub(ENTRY_PATTERN, sanitize_message, transcript))
与其有很多不同的词条,你可以有一个顶级的词条来匹配整个行,用大括号(())把它分成捕获组。捕获组对实际的匹配过程没有影响,但它们会影响匹配结果的匹配对象。
\[(.+)\]匹配任何由方括号包裹的字符序列。捕获组会挑选出用户名字符串,例如 。johndoe[-T:+\d]{25}匹配时间戳,这一点你在上一节中探讨过。因为你不会在最后的记录中使用时间戳,所以它不会用大括号捕获。:匹配一个字面的冒号。冒号是作为消息元数据和消息本身之间的分隔符。(.+)匹配任何字符序列,直到该行的末尾,这将是该消息。
通过调用.groups() 方法,捕获组的内容将作为匹配对象中的单独项目,该方法返回一个匹配字符串的元组。
注意:入口处的 regex 定义使用 Python 的隐式字符串连接法。
ENTRY_PATTERN = (
r"\[(.+)\] " # User string, discarding square brackets
r"[-T:+\d]{25} " # Time stamp
r": " # Separator
r"(.+)" # Message
)
在功能上,这与把它全部写成一个单一的字符串相同:r"\[(.+)\] [-T:+\d]{25} : (.+)" 。将较长的 regex 模式组织在不同的行中,允许你将它分成几块,这不仅使它更容易阅读,而且也允许你插入注释。
这两组是用户字符串和消息。.groups() 方法将它们作为一个字符串的元组返回。在sanitize_message() 函数中,你首先使用解包将这两个字符串分配给变量。
def sanitize_message(match):
user, message = match.groups()
return f"{censor_users(user):<6} : {censor_bad_words(message)}"
请注意这种结构是如何在顶层允许一个非常广泛和包容的词组,然后让你在替换回调中用更精确的词组来补充它。
sanitize_message() 函数使用了两个函数来清理用户名和坏词。此外,它还使用f-strings来证明信息的合理性。请注意censor_bad_words() 是如何使用动态创建的词组的,而censor_users() 则是依靠更基本的字符串处理。
现在看来,这是一个很好的誊本消毒脚本的第一个原型。输出是非常干净的。
$ python transcript_regex_callback.py
Agent : What can I help you with?
Client : I CAN'T CONNECT TO MY 😤 ACCOUNT
Agent : Are you sure it's not your caps lock?
Client : 😤! You're right!
很好!使用带有回调的sub() ,给了你更大的灵活性来混合和匹配不同的方法,并动态地建立重码。当你的老板或客户不可避免地改变对你的要求时,这种结构也给了你最大的发展空间。
摘要
在本教程中,你已经学会了如何在 Python 中替换字符串。一路走来,你已经从使用基本的 Python.replace() 字符串方法到使用带有re.sub() 的回调来实现绝对控制。你还探索了一些 regex 模式,并将它们解构为一个更好的架构来管理一个替换脚本。
有了这些知识,你已经成功地清理了一份聊天记录,现在已经可以进行独立审查了。不仅如此,你的成绩单净化脚本还有很大的发展空间。