Python 旋风之旅—字符串操作和正则表达式

Python 语言真正发光点之一是字符串的操作。 本节将介绍一些 Python 的内置字符串方法和格式设置操作,然后再继续简要介绍正则表达式极其有用的主题。 这种字符串操作模式通常会在数据科学工作的场景中出现,并且在这种情况下是 Python 的一大优势。

可以使用单引号或双引号(在功能上是等效)定义 Python 中的字符串:

x = 'a string'
y = "a string"
x == y
True

另外,可以使用三引号语法定义多行字符串:

multiline = """
one
two
three
"""

从这里,让我们快速浏览一些 Python 的字符串操作工具。

 

Python 中的简单字符串处理

对于字符串的基本操作,使用 Python 的内置字符串方法非常方便。 如果您有使用 C 或其他低级语言进行工作的背景,则可能会发现 Python 方法的简单性令人耳目一新。 前面我们介绍了Python的字符串类型和其中一些方法。 在这里,我们将进一步深入

 

格式化字符串:调整大小写

在 Python 中调整字符串的大小写非常容易。 在这里,我们以以下凌乱的字符串为例,学习 upper(), lower(), capitalize(), title(), 和 swapcase()方法:

fox = "tHe qUICk bROWn fOx."

要将整个字符串转换为大写或小写,可以分别使用 upper() 或 lower() 方法:

fox.upper()
'THE QUICK BROWN FOX.'
fox.lower()
'the quick brown fox.'

常见的格式设置需求是仅将每个单词的首字母大写,或者将每个句子的首字母大写。 这可以通过 title() 和 capitalize() 方法完成:

fox.title()
'The Quick Brown Fox.'
fox.capitalize()
'The quick brown fox.'

可以使用swapcase() 方法交换大小写:

fox.swapcase()
ThE QuicK BrowN FoX.'

 

格式化字符串:添加和删除空格

另一个常见的需求是从字符串的开头或结尾删除空格(或其他字符)。 删除字符的基本方法是 strip() 方法,该方法从行的开头和结尾删除空格:

line = '         this is the content         '
line.strip()
'this is the content'

要仅删除右侧或左侧的空格,请分别使用rstrip() 或lstrip():

line.rstrip()
'         this is the content'
line.lstrip()
'this is the content         '

要删除空格以外的字符,可以将所需的字符传递给strip() 方法:

num = "000000000000435"
num.strip('0')
'435'

可以使用 center(),  ljust(), 和 rjust() 方法完成此操作的相反操作,即添加空格或其他字符。

例如,我们可以使用 center() 方法在给定数量的空格内将给定字符串居中:

line = "this is the content"
line.center(30)
'     this is the content      '

同样,ljust() 和 rjust() 将在给定长度的空格内将字符串左对齐或右对齐:

line.ljust(30)
'this is the content           '
line.rjust(30)
'           this is the content'

所有这些方法还接受除空格外的任何其他字符用于填充。 例如:

'435'.rjust(10, '0')
'0000000435'

由于填充零是一种常见的需求,Python 还提供了 zfill(),这是一种用零右填充字符串的特殊方法:

'435'.zfill(10)
'0000000435'

 

查找和替换子字符串

如果要查找字符串中某个字符的出现,则 find()/rfind(), index()/rindex(), 和 replace() 方法是最好的内置方法。
find() 和 index() 非常相似,因为它们搜索字符串中字符或子字符串的首次出现,并返回子字符串的索引:

line = 'the quick brown fox jumped over a lazy dog'
line.find('fox')
16
line.index('fox')

16

find() 和 index() 之间的唯一区别是没有找到搜索字符串时它们的行为。 find() 返回 -1,而index() 引发ValueError:

line.find('bear')
-1
line.index('bear')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-21-4cbe6ee9b0eb> in <module>()
----> 1 line.index('bear')

ValueError: substring not found

相关的 rfind() 和 rindex() 的工作方式类似,只是它们从字符串的末尾而不是开头搜索第一个匹配项:

line.rfind('a')
35

对于在字符串的开头或结尾检查子字符串的特殊情况,Python提供了startswith() 和endswith() 方法:

line.endswith('dog')
True
line.startswith('fox')
False

要更进一步,使用新的字符串替换给定的子字符串,可以使用replace() 方法。 在这里,让我们用“read”替换为“brown”:

line.replace('brown', 'red')
'the quick red fox jumped over a lazy dog'

replace() 函数返回一个新字符串,并将替换所有匹配的字串:

line.replace('o', '--')
'the quick br--wn f--x jumped --ver a lazy d--g'

有关 replace() 函数的更灵活方法,请参见“灵活模式匹配:正则表达式”中对正则表达式的讨论。

 

分割字串(Splitting and partitioning strings)

如果您想找到一个子字符串,然后根据其位置拆分字符串,则可以使用partition() 和/或 split() 方法。 两者都将返回一个子字符串序列。

partition() 方法返回包含三个元素的元组:第一个分割点实例之前的子字符串,分割点本身以及之后的子字符串:

line.partition('fox')
('the quick brown ', 'fox', ' jumped over a lazy dog')

rpartition() 方法与此类似,但是从字符串的右边搜索。

split() 方法可能更有用; 它查找拆分点的所有实例,并返回之间的子字符串。 缺省值是在任何空格上分割,返回字符串中各个单词的列表:

line.split()
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']

一个相关的方法是splitlines(),它分割换行符。 让我们用Haiku来做这件事,Haiku 通常被认为是17世纪诗人MatsuoBashō的作品:

haiku = """matsushima-ya
aah matsushima-ya
matsushima-ya"""

haiku.splitlines()
['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']

请注意,如果要与 split() 相反的功能,则可以使用 join() 方法,该方法返回从拆分点和可迭代对象构建的字符串:

'--'.join(['1', '2', '3'])
'1--2--3'

一种常见的模式是使用特殊字符“ \n”(换行符)将先前已拆分的行连接在一起,并恢复:

print("\n".join(['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']))
matsushima-ya
aah matsushima-ya
matsushima-ya

 

格式化字符串

在前面的方法中,我们学习了如何从字符串中提取值,以及如何将字符串本身转换为所需的格式。 字符串方法的另一种用途是将其他类型的值转换为字符串。 当然,始终可以使用 str() 函数来转换成字符串。 例如:

pi = 3.14159
str(pi)
'3.14159'

对于更复杂的格式,您可能会倾向于使用“Python 基本语义:运算符”中概述的字符串运算:

"The value of pi is " + str(pi)
'The value of pi is 3.14159'

一种更灵活的方法是使用格式字符串,即带有特殊标记(用花括号{}表示)的字符串,字符串格式的值将插入其中。 这是一个基本示例:

"The value of pi is {}".format(pi)
'The value of pi is 3.14159'

在{}标记内,您还可以包含有关您希望在此处显示的内容的信息。 如果包含数字,它将引用要插入的参数的索引:

"""First letter: {0}. Last letter: {1}.""".format('A', 'Z')
'First letter: A. Last letter: Z.'

如果包含字符串,它将作为关键字参数的键:

"""First letter: {first}. Last letter: {last}.""".format(last='Z', first='A')
'First letter: A. Last letter: Z.'

最后,对于数字输入,可以包括格式代码,这些代码控制如何将值转换为字符串。 例如,要将数字打印为小数点后三位数的浮点数,可以使用以下格式:

"pi = {0:.3f}".format(pi)
'pi = 3.142'

如前所述,此处的“ 0”是指要插入的值的索引。 “:”标记将遵循格式代码。 “ .3f”表示所需的精度:小数点后三位数,浮点格式。

这种格式化非常灵活,此处的示例几乎不涉及可用的格式设置选项。 有关这些格式字符串的语法的更多信息,请参见Python的在线文档的“格式规范”部分。

 

灵活模式匹配:正则表达式

Python的 str 类型的方法提供了一套功能强大的工具,用于格式化,分割和处理字符串数据。 但是 Python 的内置正则表达式模块中提供了更强大的工具。 正则表达式是一个巨大的主题。 涉及该主题的书籍够写整本书(包括Jeffrey E.F. Friedl的Mastering Regular Expressions,第3版),因此仅靠一个小节就很难做到欣赏他的强大功能。

我的目标是让您了解可以使用正则表达式解决哪些类型的问题,以及有关如何在 Python 中使用它们的基本概念。我将在“正则表达式的更多资源” 中给出更多的学习建议。

从根本上来说,正则表达式是字符串中灵活的模式匹配的一种方式。 如果您经常使用命令行,则可能熟悉这种带有“ *”字符(用作通配符)的灵活匹配。 例如,我们可以使用“ *”通配符来匹配所有IPython笔记(即扩展名为.ipynb的文件),其文件名中带有“ Python”:

在 jupyter notebook 中执行命令:

!ls *Python*.ipynb
01-How-to-Run-Python-Code.ipynb 02-Basic-Python-Syntax.ipynb

正则表达式将这种“通配符”概念概括为各种灵活的字符串匹配语法。Python的正则表达式接口包含在内置的 re 模块中; 作为一个简单的示例,让我们使用它来复制字符串 split() 方法的功能:

import re
regex = re.compile('\s+')
regex.split(line)
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']

在这里,我们首先编译了一个正则表达式,然后使用它来分割字符串。 就像 Python 的 split() 方法返回空格之间的所有子字符串的列表一样,正则表达式 split() 方法返回与输入模式匹配的所有子字符串的列表。

在这种情况下,输入为 "\s+": "\s" 是与任何空格(空格,制表符,换行符等)匹配的特殊字符,而"+"是指示一个或多个它前面的实体。 因此,该正则表达式匹配由一个或多个空格组成的任何子字符串。

这里的 split() 方法基本上是基于此模式匹配行为的便捷方法; 更基本的是 match() 方法,它将告诉您字符串的开头是否与模式匹配:

for s in ["     ", "abc  ", "  abc"]:
    if regex.match(s):
        print(repr(s), "matches")
    else:
        print(repr(s), "does not match")
'     ' matches
'abc  ' does not match
'  abc' matches

像split()一样,也有类似的便利例程来查找第一个匹配项(例如str.index()或str.find())或查找并替换(例如str.replace())。 我们将再次使用之前的字串:

line = 'the quick brown fox jumped over a lazy dog'

这样,我们可以看到regex.search() 方法的操作方式与str.dex()或str.find()相似:

line.index('fox')
16
regex = re.compile('fox')
match = regex.search(line)
match.start()
16

同样,regex.sub()方法的操作与str.replace()的操作非常相似:

line.replace('fox', 'BEAR')
'the quick brown BEAR jumped over a lazy dog'
regex.sub('BEAR', line)
'the quick brown BEAR jumped over a lazy dog'

稍加思考,其他字符串内置方法的操作也都可以转换为正则表达式。

 

一个更复杂的例子

但是,您可能会问,为什么要使用更复杂和冗长的正则表达式语法而不是更直观和简单的字符串方法? 因为正则表达式提供了更大的灵活性。

在这里,我们将考虑一个更复杂的示例:匹配电子邮件地址的常见任务。 我将首先简单地编写一个(有些难以理解的)正则表达式,然后逐步分析。 从这里开始:

email = re.compile('\w+@\w+\.[a-z]{3}')

使用此方法,如果从文档中得到一行如下的字串,我们可以快速提取类似于电子邮件地址的内容

text = "To email Guido, try [email protected] or the older address [email protected]."
email.findall(text)
['[email protected]', '[email protected]']

(请注意,这些地址是虚构的。 可能有更好的方法与Guido联系)

我们可以做进一步的操作,例如用另一个字符串替换这些电子邮件地址,比如在输出中隐藏地址:

email.sub('[email protected]', text)
'To email Guido, try [email protected] or the older address [email protected].'

最后,请注意,如果您确实要匹配任何电子邮件地址,则前面的正则表达式太简单了。 例如,它仅允许由字母数字字符组成的地址以几个常见域后缀之一结尾。 因此,例如,此处使用的句点意味着我们仅找到地址的一部分:

email.findall('[email protected]')
['[email protected]']

这说明如果您不小心,正则表达式会变得多么无情! 如果您在网上搜索,可以找到一些与所有有效电子邮件匹配的正则表达式建议,但请注意:它们比此处使用的简单表达式要复杂得多!

 

正则表达式的基本语法

对于这一小节,正则表达式的语法太大了。 尽管如此,一定的熟悉度还有很长的路要走:我将在这里介绍一些基本的构造,然后列出一些更完整的资源,您可以从中学习更多。 我希望以下快速入门可以使您有效地使用这些资源。

 

简单字符串直接匹配

如果您用一个简单的字符串或数字字符串构建正则表达式,它将与该字符串完全匹配:

regex = re.compile('ion')
regex.findall('Great Expectations')
['ion']

 

有特殊含义的字符

虽然简单的字母或数字是直接匹配项,但是在正则表达式中有少数几个具有特殊含义的字符。 他们是:

. ^ $ * + ? { } [ ] \ | ( )

我们将立刻讨论其中一些的含义。 同时,您应该知道,如果您想直接匹配这些字符中的任何一个,则可以使用反斜杠(\)对其进行转义:

regex = re.compile(r'\$')
regex.findall("the cost is $20")
['$']

r'\$'中的前缀 r 表示原始字符串; 在标准Python字符串中,反斜杠用于表示特殊字符。 例如,制表符卡用 "\t"表示:

print('a\tb\tc')
a	b	c

在原始字符串中是不会进行此类替换的:

print(r'a\tb\tc')
a\tb\tc

因此,每当在正则表达式中使用反斜杠时,最好使用原始字符串。

 

特殊字符可以匹配一组字符

就像正则表达式中的“ \”字符可以转义特殊字符,将其转换为普通字符一样,它也可以用于赋予普通字符特殊的含义。 这些特殊字符与指定的字符组匹配,我们之前已经见过它们。 在之前的电子邮件地址正则表达式中,我们使用了字符“ \w”,这是与任何字母数字字符匹配的特殊标记。 同样,在简单的split()示例中,我们还看到了“ \s”,这是一个特殊的标记,指示任何空白字符。

将它们放在一起,我们可以创建一个正则表达式,该正则表达式将匹配任意两个之间具有空格的字母/数字:

regex = re.compile(r'\w\s\w')
regex.findall('the fox is 9 years old')
['e f', 'x i', 's 9', 's o']

此示例开始暗示正则表达式的强大功能和灵活性。

下表列出了其中一些常用的特殊含义的字符:

                            

这不是完整的列表或描述; 有关更多详细信息,请参见Python的正则表达式语法文档

 

方括号匹配自定义字符组

如果内置字符组对您来说不够具体,则可以使用方括号指定您感兴趣的任何字符集。例如,以下内容将匹配任何小写的元音:

regex = re.compile('[aeiou]')
regex.split('consequential')
['c', 'ns', 'q', '', 'nt', '', 'l']

同样,您可以使用破折号指定范围:例如, "[a-z]"将匹配任何小写字母,而“ [1-3]”将匹配“ 1”,“ 2”或“ 3“。 例如,您可能需要从文档中提取特定的数字代码,该数字代码由大写字母后跟数字组成。 您可以按照以下步骤进行操作:

regex = re.compile('[A-Z][0-9]')
regex.findall('1043879, G2, H6')
['G2', 'H6']

通配符匹配重复字符

如果您想在一个字符串匹配三个连续的字母数字字符,则可以编写例如"\w\w\w"。 因为这是一个普遍的需求,所以有一种特定的语法可以匹配重复项—带数字的花括号:

regex = re.compile(r'\w{3}')
regex.findall('The quick brown fox')
['The', 'qui', 'bro', 'fox']

还有一些标记可用于匹配任意数量的重复-例如,“ +”字符将匹配其前面的一个或多个重复:

regex = re.compile(r'\w+')
regex.findall('The quick brown fox')
['The', 'quick', 'brown', 'fox']

下表是可用于正则表达式的重复标记的表:

     

牢记这些基础,让我们回到我们的电子邮件地址匹配器:

email = re.compile(r'\w+@\w+\.[a-z]{3}')

现在我们可以理解这是什么意思:我们想要一个或多个字母数字字符 ("\w+") ,然后是at符号("@"), 然后是一个或多个字母数字字符(("\w+"),再加上句点 ("\."  –注意需要反斜杠转义),后面紧跟三个小写字母。

如果现在要修改此正则表达式以使奥巴马的电子邮件地址匹配,则可以使用方括号表示法:

email2 = re.compile(r'[\w.]+@\w+\.[a-z]{3}')
email2.findall('[email protected]')
['[email protected]']

我们将“"\w+" 更改为 "[\w.]+",因此我们将匹配任何字母数字字符或句点。 借助这种更加灵活的表达方式,我们可以匹配更广泛的电子邮件地址(尽管还不是全部-您是否可以识别此表达方式的其他缺陷?)。

 

括号表示要提取的组

对于复合正则表达式(例如我们的电子邮件匹配器),我们通常希望提取其部分而不是匹配的完全。 可以使用括号将结果分组:

email3 = re.compile(r'([\w.]+)@(\w+)\.([a-z]{3})')
text = "To email Guido, try [email protected] or the older address [email protected]."
email3.findall(text)
[('guido', 'python', 'org'), ('guido', 'google', 'com')]

如我们所见,该分组实际上提取了电子邮件地址的子部分列表。

我们可以更进一步,使用"(?P<name> )" 语法,命名提取的部分,在这种情况下,可以将分组提取为Python字典:

email4 = re.compile(r'(?P<user>[\w.]+)@(?P<domain>\w+)\.(?P<suffix>[a-z]{3})')
match = email4.match('[email protected]')
match.groupdict()
{'domain': 'python', 'suffix': 'org', 'user': 'guido'}

结合这些想法(以及我们此处未介绍的一些强大的regexp语法),您可以灵活,快速地从Python的字符串中提取信息。

 

正则表达式的更多资源

上面的讨论只是对这个大话题的简明介绍(而且还远远没有包括全部知识)。 如果您想了解更多信息,我建议以下资源:

  • Python 的 re 包文档:我发现我会很快忘记每次使用正则表达式时都该如何使用它们。 既然我已经掌握了基础知识,那么我发现此页面是一个非常有价值的资源,可以用来回忆起每个特定字符或序列在正则表达式中的含义。
  • Python的正式正则表达式HOWTO:Python中正则表达式的一种更具叙述性的方法。
  • 精通正则表达式(OReilly,2006年):是一本有关该主题的500多页的书。 如果您想要真正完整地处理此主题,这是为您准备的资源。

有关大规模使用字符串操作和正则表达式的一些示例,请参见Pandas:面向列的标签数据,我们将在通过 Pandas包中的字符串数据表应用这些类型的表达式。

 

本文来自翻译如下文章,仅用于学习

原文:

https://nbviewer.jupyter.org/github/jakevdp/WhirlwindTourOfPython/blob/master/14-Strings-and-Regular-Expressions.ipynb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章