文章目录

  • 1 问题
  • 2. 解决方案
  • 3. 讨论



1 问题

你希望匹配或搜索文本中的特定模式。

2. 解决方案

如果你希望匹配或搜索的文本是简单的字面量,那么你通常可以使用基本的字符串方法,例如: str.find()str.endswith()str.startswith() ,例如:

>>> text = 'yeah, but no, but yeah, but no, but yeah'
>>> # 完全匹配
>>> text == 'yeah'
False
>>> # 匹配开头或结尾
>>> text.startswith('yeah')
True
>>> text.endswith('no')
False
>>> # 查找子串第一次出现的位置
>>> text.find('no')
10

对于更加复杂的匹配,则需要结合 re 模块以及正则表达式。为了演示使用正则表达式的基本原理,假设你希望匹配以数字形式给定的日期,如: 11/27/2012 。例如:

>>> text1 = '11/27/2012'
>>> text2 = 'Nov 27, 2012'
>>> import re
>>> # 简单的正则:\d+ 表示一位或多位数字
>>> if re.match(r'\d+/\d+/\d+', text1):
...     print('yes')
... else:
...     print('no')
...     
yes
>>> if re.match(r'\d+/\d+/\d+', text2):
...     print('yes')
... else:
...     print('no')
...     
no

如果你要使用同一个正则表达式进行很多次的匹配查找,那么通过将该正则表达式进行预编译可以提高匹配的速度,例如:

>>> date_pattern = re.compile(r'\d+/\d+/\d+')
>>> if date_pattern.match(text1):
...     print('yes')
... else:
...     print('no')
...     
yes

>>> if date_pattern.match(text2):
...     print('yes')
... else:
...     print('no')
...     
no

上述 match() 方法总是尝试从字符串的开头进行匹配,且只返回第一个找到的结果。如果你希望找到符合正则表达式模式的所有结果,那么你需要使用 findall() 方法。例如:

>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> date_pattern.findall(text)
['11/27/2012', '3/13/2013']

需要注意的是,在定义正则表达式时,经常会将正则表达式的一部分用圆括号括起来,这就是所谓的捕捉分组。例如:

date_pattern = re.compile(r'(\d+)/(\d+)/(\d+)')

捕捉分组通常可以简化正则匹配成功后的一系列操作,因为可以很方便地将每个分组中的内容单独提取出来。例如:

>>> date_pattern = re.compile(r'(\d+)/(\d+)/(\d+)')
>>> result = date_pattern.match('11/27/2012')
>>> result
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> result.group(0)
'11/27/2012'
>>> result.group(1)
'11'
>>> result.group(2)
'27'
>>> result.group(3)
'2012'
>>> result.group()
'11/27/2012'
>>> result.groups()
('11', '27', '2012')

>>> text
'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> date_pattern.findall(text)
[('11', '27', '2012'), ('3', '13', '2013')]
>>> date_pattern = re.compile(r'(\d+)/(\d+)/\d+')
>>> date_pattern.findall(text)
[('11', '27'), ('3', '13')]

findall() 方法会一次性查找出所有匹配的结果然后以列表的形式进行返回。如果你希望通过类似迭代器的方式查找并返回匹配的结果,你可以使用 finditer() 方法。例如:

>>> date_pattern.finditer(text)
<callable_iterator object at 0x0000000003F2D1C0>
>>> list(date_pattern.finditer(text))
[<re.Match object; span=(9, 19), match='11/27/2012'>, <re.Match object; span=(34, 43), match='3/13/2013'>]
>>> for each in date_pattern.finditer(text):
...     print(each.groups())
...     
('11', '27', '2012')
('3', '13', '2013')

3. 讨论

关于正则表达式的使用并非本文的重点,然而本文介绍了使用 re 模块实现文本搜索和匹配的基本步骤,即首先使用 re.compile() 函数对正则表达式进行编译,接着使用编译后得到的对象的 match()findall() 以及 finditer() 等方法。

如果你只是做一次简单的文本匹配和搜索操作,那么你可以直接跳过编译的步骤而使用 re 模块的各个同名方法。例如:

>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> re.findall(r'(\d+)/(\d+)/(\d+)', text)
[('11', '27', '2012'), ('3', '13', '2013')]