如何检查一个Python字符串是否包含一个子串

360 阅读15分钟

How to Check if a Python String Contains a Substring

如何检查一个Python字符串是否包含一个子串

如果你是编程新手或者来自Python以外的编程语言,你可能正在寻找在Python中检查一个字符串是否包含另一个字符串的最佳方法。

当你在处理文件中的文本内容时,或者在收到用户的输入后,识别这样的子串会很方便。你可能想在你的程序中根据子串的存在与否执行不同的操作。

在本教程中,你将重点介绍处理这一任务的最Pythonic方式,即使用成员运算符in此外,你将学习如何为相关但不同的用例确定正确的字符串方法

最后,你还将学习如何在pandas列中寻找子串。如果你需要从CSV文件中搜索数据,这很有帮助。你可以使用你将在下一节中学习的方法,但是如果你正在处理表格数据,最好将数据加载到pandas DataFrame中,然后在pandas中搜索子串

如何确认一个Python字符串包含另一个字符串

如果你需要检查一个字符串是否包含一个子串,可以使用Python的成员运算符in 。在Python中,这是确认一个字符串中是否存在子串的推荐方法。

>>>

>>> raw_file_content = """Hi there and welcome.
... This is a special hidden file with a SECRET secret.
... I don't want to tell you The Secret,
... but I do want to secretly tell you that I have one."""

>>> "secret" in raw_file_content
True

in 成员运算符给你提供了一种快速和可读的方法来检查字符串中是否存在子串。你可能会注意到,这行代码几乎读起来像英语。

注意:如果你想检查子串是否在字符串中,那么你可以使用not in

>>>

>>> "secret" not in raw_file_content
False

因为子串"secret" 存在于raw_file_content 中,所以not in 操作符返回False

当你使用in 时,表达式会返回一个布尔值

  • True 如果Python找到了这个子串
  • False 如果Python没有找到该子串

你可以在条件语句中使用这种直观的语法,在你的代码中做出决定。

>>>

>>> if "secret" in raw_file_content:
...    print("Found!")
...
Found!

在这个代码片断中,你使用成员运算符来检查"secret" 是否是raw_file_content 的子串。如果是,那么你将向终端打印一条信息。任何缩进的代码只有在你所检查的 Python 字符串包含你所提供的子串时才会执行。

如果你只需要检查一个Python字符串是否包含一个子串,成员运算符in 是你最好的朋友。

然而,如果你想知道关于子串的更多信息呢?如果你阅读了存储在raw_file_content 中的文本,那么你会注意到子串出现了不止一次,甚至是以不同的变化形式出现的!

在这些出现的情况中,Python 找到了哪些?大写字母有区别吗?子串在文本中出现的频率如何?还有,这些子串的位置是什么?如果你需要这些问题的答案,那么请继续阅读。

通过去除大小写敏感度来泛化你的检查

Python 字符串是区分大小写的。如果你提供的子串使用的大写字母与你文本中的同一个词不同,那么 Python 不会找到它。例如,如果你在原始文本的标题大写版本上检查小写的单词"secret" ,成员运算符检查返回False

>>>

>>> title_cased_file_content = """Hi There And Welcome.
... This Is A Special Hidden File With A Secret Secret.
... I Don't Want To Tell You The Secret,
... But I Do Want To Secretly Tell You That I Have One."""

>>> "secret" in title_cased_file_content
False

尽管秘密这个词在标题大写的文本title_cased_file_content 中出现了多次,但它从未以全小写的形式出现过。这就是为什么你用成员运算符进行的检查返回False 。Python 在提供的文本中找不到全小写的字符串"secret"

人类对语言的态度与计算机不同。这就是为什么当你在 Python 中检查一个字符串是否包含一个子串时,你经常想不考虑大写字母。

你可以通过将整个输入文本转换为小写字母来概括你的子串检查。

>>>

>>> file_content = title_cased_file_content.lower()

>>> print(file_content)
hi there and welcome.
this is a special hidden file with a secret secret.
i don't want to tell you the secret,
but i do want to secretly tell you that i have one.

>>> "secret" in file_content
True

将你的输入文本转换为小写字母是一种常见的方法,因为人类认为只有大写字母不同的词是同一个词,而计算机则不这么认为。

注意:在下面的例子中,你将继续使用file_content ,即你的文本的小写版本。

如果你用原始字符串(raw_file_content)或标题大写的字符串(title_cased_file_content)工作,那么你会得到不同的结果,因为它们不是小写的。在你完成这些例子的时候,请随意试一试吧

现在你已经将字符串转换为小写字母,以避免因大小写敏感而产生的意外问题,现在是时候进一步挖掘和学习更多关于子串的知识。

了解更多关于子串的信息

成员运算符in 是一个很好的方法来描述性地检查一个字符串中是否有子串,但是它并没有给你更多的信息。它非常适合于条件性检查--但是如果你需要知道更多关于子串的信息呢?

Python 提供了许多附加的字符串方法,允许你检查字符串包含多少个目标子串,根据精心设计的条件搜索子串,或者定位文本中子串的索引。

在本节中,你将介绍一些额外的字符串方法,它们可以帮助你了解更多关于子串的信息。

注意:你可能已经看到下面的方法被用来检查一个字符串是否包含一个子串。这是有可能的--但它们并不是用来做这个的!

编程是一种创造性的活动,你总是可以找到不同的方法来完成同样的任务。然而,为了你的代码的可读性,最好是按照你所使用的语言的原意来使用这些方法。

通过使用in ,你确认了字符串包含子串。但是你并没有得到任何关于子串的位置的信息。

如果你需要知道子串在字符串中出现的位置,那么你可以在字符串对象上使用.index()

>>>

>>> file_content = """hi there and welcome.
... this is a special hidden file with a secret secret.
... i don't want to tell you the secret,
... but i do want to secretly tell you that i have one."""

>>> file_content.index("secret")
59

当你在字符串上调用.index() 并将子串作为参数传给它时,你会得到子串第一次出现的第一个字符的索引位置。

注意:如果 Python 不能找到子串,那么.index() 会引发一个ValueError [异常]。

但是如果你想找到子串的其他出现的位置呢?.index() 方法还需要一个第二个参数,可以定义从哪个索引位置开始查找。通过传递特定的索引位置,你可以跳过你已经确定的子串的出现。

>>>

>>> file_content.index("secret", 60)
66

当你传递一个超过子串第一次出现的起始索引时,Python 将从那里开始搜索。在这种情况下,你得到的是另一个匹配,而不是一个ValueError

这意味着文本包含子串不止一次。但是它在里面有多长时间呢?

你可以使用.count() ,用描述性的和习惯性的Python代码快速得到你的答案。

>>>

>>> file_content.count("secret")
4

你在小写字母字符串上使用了.count() ,并将子串"secret" 作为参数传递。Python 计算了子串在字符串中出现的频率,并返回了答案。该文本包含了四次子串。但是这些子串是什么样子的呢?

你可以通过在默认的单词边界处分割你的文本并使用for 循环将单词打印到终端来检查所有的子串。

>>>

>>> for word in file_content.split():
...    if "secret" in word:
...        print(word)
...
secret
secret.
secret,
secretly

在这个例子中,你用 .split()将空白处的文本分离成字符串,Python 将其打包成一个列表。然后你遍历这个列表,在每个字符串上使用in 来查看它是否包含子串"secret"

注意:你也可以不打印子串,而是将它们保存在一个新的列表中,例如通过使用带有条件表达式的列表理解。

>>>

>>> [word for word in file_content.split() if "secret" in word]
['secret', 'secret.', 'secret,', 'secretly']

在这种情况下,你只从包含子串的词中建立一个列表,这基本上是对文本的过滤。

现在你可以检查 Python 识别的所有子串,你可能注意到 Python 并不关心子串后面是否有任何字符"secret" 。它可以找到这个词,不管它后面是空白还是标点符号。它甚至可以找到像"secretly" 这样的词。

知道这些很好,但是如果你想在子串检查上设置更严格的条件,你可以做什么呢?

使用Regex查找有条件的子串

你可能只想匹配出现在你的子串后面的标点符号,或者识别包含子串和其他字母的词,例如"secretly"

对于这种需要更多的字符串匹配的情况,你可以使用Python的re 模块,使用正则表达式,或者叫regex。

例如,如果你想找到所有以"secret" 开头但后面至少有一个字母的词,那么你可以使用 regex字符(\w) 后面的加号量词(+) 。

>>>

>>> import re

>>> file_content = """hi there and welcome.
... this is a special hidden file with a secret secret.
... i don't want to tell you the secret,
... but i do want to secretly tell you that i have one."""

>>> re.search(r"secret\w+", file_content)
<re.Match object; span=(128, 136), match='secretly'>

re.search() 函数同时返回符合条件的子串以及它的开始和结束索引位置--而不仅仅是True!

然后你可以通过 Match 对象上的方法来访问这些属性,该对象m 表示。

>>>

>>> m = re.search(r"secret\w+", file_content)

>>> m.group()
'secretly'

>>> m.span()
(128, 136)

这些结果给了你很大的灵活性来继续处理匹配的子串。

例如,你可以只搜索后面有逗号的子串 (,) 或有句号的子串 (.) 。

>>>

>>> re.search(r"secret[\.,]", file_content)
<re.Match object; span=(66, 73), match='secret.'>

在你的文本中有两个潜在的匹配,但是你只匹配了符合你查询的第一个结果。当你使用re.search() 时,Python 又只找到了第一个匹配结果。如果你想要所有符合某个条件的关于"secret" 的提法呢?

为了使用re 找到所有的匹配,你可以使用re.findall()

>>>

>>> re.findall(r"secret[\.,]", file_content)
['secret.', 'secret,']

通过使用re.findall() ,你可以找到文本中所有匹配的模式。Python 为你把所有匹配的字符串保存在一个列表中。

当你使用捕获组时,你可以通过将匹配的哪一部分包裹在小括号中来指定你想保留在你的列表中。

>>>

>>> re.findall(r"(secret)[\.,]", file_content)
['secret', 'secret']

通过将秘密包裹在小括号中,你定义了一个单一的捕获组。只要模式中正好有一个捕获组,findall() 函数就会返回一个与该捕获组相匹配的字符串列表。通过在secret周围加上小括号,你成功地摆脱了标点符号的束缚。

注意:记住,在你的文本中有四个子串"secret" ,通过使用re ,你过滤掉了两个特定的出现,你根据特殊条件进行了匹配。

使用re.findall() 与匹配组是一种从文本中提取子串的强大方式。但是你只能得到一个字符串的列表,这意味着你已经失去了在使用re.search() 时可以访问的索引位置。

如果你想保留这些信息,那么re 可以给你一个迭代器中的所有匹配信息。

>>>

>>> for match in re.finditer(r"(secret)[\.,]", file_content):
...    print(match)
...
<re.Match object; span=(66, 73), match='secret.'>
<re.Match object; span=(103, 110), match='secret,'>

当你使用re.finditer() ,并将搜索模式和你的文本内容作为参数传递给它时,你可以访问每个包含子串的Match 对象,以及它的开始和结束索引位置。

你可能会注意到,即使你仍在使用捕获组,标点符号也会显示在这些结果中。这是因为Match 对象的字符串表示法显示了整个匹配,而不仅仅是第一个捕获组。

但是,Match 对象是一个强大的信息容器,就像你前面看到的那样,你可以只挑选出你需要的信息。

>>>

>>> for match in re.finditer(r"(secret)[\.,]", file_content):
...    print(match.group(1))
...
secret
secret

通过调用 .group()并指定你要第一个捕获组,你就从每个匹配的子串中挑选了不带标点符号的单词secret

当你使用正则表达式时,你可以对你的子串匹配进行更详细的研究。你可以根据精心设计的条件搜索子串,而不仅仅是检查一个字符串是否包含另一个字符串。

注意:如果你想了解更多关于使用捕获组和组成更复杂的正则表达式模式,那么你可以深入研究Python 中的正则表达式

如果你需要关于子串的信息,或者你需要在文本中找到子串后继续处理它们,那么用re 的正则表达式是一个好方法。但是如果你在处理表格数据时怎么办?为此,你会求助于pandas。

在pandas DataFrame列中查找子串

如果你处理的数据不是来自纯文本文件或用户输入,而是来自CSV文件Excel表格,那么你可以使用上面讨论的相同方法。

然而,有一种更好的方法来识别一列中的哪些单元格包含子串:你将使用pandas!在这个例子中,你将使用一个包含假公司名称和口号的CSV文件。如果你想一起工作,你可以下载下面的文件。

当你在Python中处理表格数据时,通常最好先将其加载到pandasDataFrame

>>>

>>> import pandas as pd

>>> companies = pd.read_csv("companies.csv")

>>> companies.shape
(1000, 2)

>>> companies.head()
             company                                     slogan
0      Kuvalis-Nolan      revolutionize next-generation metrics
1  Dietrich-Champlin  envisioneer bleeding-edge functionalities
2           West Inc            mesh user-centric infomediaries
3         Wehner LLC               utilize sticky infomediaries
4      Langworth Inc                 reinvent magnetic networks

在这个代码块中,你将一个包含一千行虚假公司数据的CSV文件加载到一个pandas DataFrame中,并使用.head() ,检查了前五行。

注意:你需要创建一个虚拟环境安装pandas,以便使用该库工作。

在你将数据加载到DataFrame之后,你可以快速查询整个pandas列,以过滤包含子串的条目。

>>>

>>> companies[companies.slogan.str.contains("secret")]
              company                                  slogan
7          Maggio LLC                    target secret niches
117      Kub and Sons              brand secret methodologies
654       Koss-Zulauf              syndicate secret paradigms
656      Bernier-Kihn  secretly synthesize back-end bandwidth
921      Ward-Shields               embrace secret e-commerce
945  Williamson Group             unleash secret action-items

你可以在pandas列上使用 .str.contains()在一个pandas列上,把子串作为参数传给它,来过滤包含子串的行。

注意:索引操作符([])和属性操作符(.)提供了获取DataFrame的单列或片断的直观方法。

然而,如果你正在处理那些关注性能的生产代码,pandas建议使用优化的数据访问方法来索引和选择数据

当你在使用.str.contains() ,并且需要更复杂的匹配场景时,你也可以使用正则表达式!你只需要传递一个正则表达式即可。你只需要传递一个符合regex的搜索模式作为子串参数。

>>>

>>> companies[companies.slogan.str.contains(r"secret\w+")]
          company                                  slogan
656  Bernier-Kihn  secretly synthesize back-end bandwidth

在这个代码片断中,你使用了与前面相同的模式,只匹配包含秘密但又继续有一个或多个单词字符的单词(\w+)。在这个假的数据集中,只有一家公司似乎是秘密运作的!你可以写任何复杂的regex。

你可以编写任何复杂的重合模式,并将其传递给.str.contains() ,以便从你的pandas列中只雕刻出你分析所需的行。

结论

就像一个坚持不懈的寻宝者,你找到了每一个"secret" ,不管它被藏得多好!在这个过程中,你学到了在Python中检查一个字符串是否包含一个子串的最好方法是使用in 成员操作符。

你还学会了如何描述性地使用另外两个字符串方法,这两个方法经常被误用来检查子串。

  • .count() 计算子串在一个字符串中的出现次数
  • .index() 获取子串开头的索引位置

之后,你探索了如何用正则表达式和 Python 的re 模块中的一些函数根据更高级的条件来查找子串。

最后,你还学习了如何使用 DataFrame 方法.str.contains() 来检查pandas DataFrame中哪些条目包含子串。

你现在知道了当你在 Python 中处理子串时如何选择最习惯的方法。继续使用最能说明问题的方法,你将会写出令人愉快的代码,并能让别人快速理解。