如果您正在寻找在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()字符串方法:
>>> "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()。
设置多个替换规则
你还需要对成绩单进行一些替换,以使其成为独立审查可以接受的格式:
- 缩短或删除时间戳
- 将用户名替换为Agent和Client
既然您开始有更多的字符串要替换,那么链接.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(),用从每个替换元组中解压缩的旧变量和新变量填充参数。
注意:在这种情况下,for循环中的解包在功能上与使用indexing相同:
for replacement in replacements:
new_transcript = new_transcript.replace(replacement[0], replacement[1])
如果您对解包感到困惑,请查看Python列表和元组教程中的解包部分。
有了这个,你在整个文本的可读性上有了很大的改进。如果需要的话,添加替换也更容易。运行这个脚本可以得到更清晰的文本:
$ 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。
Regex是一种小型语言,由定义模式的字符组成。这些模式或正则表达式通常用于在查找、查找和替换操作中搜索字符串。许多编程语言都支持正则表达式,而且它被广泛使用。Regex甚至会给你超能力。
在Python中,利用正则表达式意味着使用re模块的sub()函数并构建自己的正则表达式模式:
# 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*”模式相当广泛,也会将成纤维细胞修改为纤维。它也无法识别这个词的礼貌用法。它只是与角色相匹配。也就是说,你想要审查的那些典型的不文明用语并没有真正的礼貌含义!
第二个正则表达式模式使用字符集和量词替换时间戳。您经常同时使用字符集和量词。例如,正则表达式模式[abc]将匹配A、b或c中的一个字符。将*直接放在它后面将匹配A、b或c中的零个或多个字符。
不过还有更多的量词。如果使用[abc]{10},它将以任何顺序和任何组合精确匹配a、b或c的10个字符。还要注意,重复字符是多余的,所以[aa]等价于[a]。
对于时间戳,使用扩展字符集[-T:+\d]来匹配可能在时间戳中找到的所有可能的字符。与量词{25}配对,它将匹配任何可能的时间戳,至少直到10,000年。
注意:特殊字符\d匹配任何数字字符。
时间戳正则表达式模式允许您在时间戳格式中选择任何可能的日期。由于时间对于这些抄本的独立审稿人来说并不重要,所以您将它们替换为一个空字符串。可以编写更高级的正则表达式,在删除日期的同时保留时间信息。
第三个正则表达式模式用于选择以关键字“support”开头的任何用户字符串。注意,您对方括号([)进行了转义(\),否则关键字将被解释为字符集。
最后,最后一个正则表达式模式选择客户端用户名字符串并将其替换为“client”。
注意:虽然更详细地讨论这些正则表达式模式会非常有趣,但本教程不是关于正则表达式的。阅读Python regex教程,了解该主题的入门知识。此外,您还可以利用出色的RegExr网站,因为正则表达式很复杂,所有级别的正则表达式向导都依赖于像RegExr这样方便的工具。
RegExr特别好,因为您可以复制和粘贴正则表达式模式,它会用解释为您分解它们。
使用正则表达式,您可以大幅减少必须编写的替换的数量。也就是说,你可能仍然需要想出许多模式。由于正则表达式不是最易读的语言,拥有大量的模式很快就会变得难以维护。
值得庆幸的是,re.sub()有一个巧妙的技巧,可以让您对替换的工作方式有更多的控制,而且它提供了一个可维护性更强的体系结构。
使用re.sub()回调来获得更多的控制
Python和sub()的一个妙招是,你可以传入一个回调函数而不是替换字符串。这使您可以完全控制如何匹配和替换。
要开始构建这个版本的转录净化脚本,您将使用一个基本的正则表达式模式来了解如何使用带有sub()的回调:
# transcript_regex_callback.py
import retranscript = """
[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模块的构建块之一。更基本的re.match()函数返回一个匹配对象。Sub()不返回任何匹配对象,而是在幕后使用它们。
因为您在回调中获得了这个匹配对象,所以您可以使用其中包含的任何信息来构建替换字符串。一旦它被构建,您将返回新的字符串,而sub()将用返回的字符串替换匹配。
将 Callback应用到脚本
在你的脚本中,你将使用匹配对象的.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()方法,捕获组的内容将作为匹配对象中的单独项可用,该方法返回匹配字符串的元组。
注意:条目正则表达式定义使用了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-string来验证消息。注意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()回调来实现绝对控制。您还探索了一些正则表达式模式,并将它们解构为更好的体系结构,以管理替换脚本。
有了这些知识,您已经成功地清理了聊天记录,现在可以进行独立审查了。不仅如此,对脚本进行消毒的脚本还有很大的发展空间。