======================== 2.4 字符串匹配和搜索 ======================== ---------- 问题 ---------- 你想匹配或者搜索特定模式的文本 ---------- 解决方案 ---------- 如果你想匹配的是字面字符串,那么你通常只需要调用基本字符串方法就行, 比如 ``str.find()`` , ``str.endswith()`` , ``str.startswith()`` 或者类似的方法: .. code-block:: python >>> text = 'yeah, but no, but yeah, but no, but yeah' >>> # Exact match >>> text == 'yeah' False >>> # Match at start or end >>> text.startswith('yeah') True >>> text.endswith('no') False >>> # Search for the location of the first occurrence >>> text.find('no') 10 >>> 对于复杂的匹配需要使用正则表达式和 ``re`` 模块。 为了解释正则表达式的基本原理,假设你想匹配数字格式的日期字符串比如 ``11/27/2012`` ,你可以这样做: .. code-block:: python >>> text1 = '11/27/2012' >>> text2 = 'Nov 27, 2012' >>> >>> import re >>> # Simple matching: \d+ means match one or more digits >>> 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 >>> 如果你想使用同一个模式去做多次匹配,你应该先将模式字符串预编译为模式对象。比如: .. code-block:: python >>> datepat = re.compile(r'\d+/\d+/\d+') >>> if datepat.match(text1): ... print('yes') ... else: ... print('no') ... yes >>> if datepat.match(text2): ... print('yes') ... else: ... print('no') ... no >>> ``match()`` 总是从字符串开始去匹配,如果你想查找字符串任意部分的模式出现位置, 使用 ``findall()`` 方法去代替。比如: .. code-block:: python >>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.' >>> datepat.findall(text) ['11/27/2012', '3/13/2013'] >>> 在定义正则式的时候,通常会利用括号去捕获分组。比如: .. code-block:: python >>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)') >>> 捕获分组可以使得后面的处理更加简单,因为可以分别将每个组的内容提取出来。比如: .. code-block:: python >>> m = datepat.match('11/27/2012') >>> m <_sre.SRE_Match object at 0x1005d2750> >>> # Extract the contents of each group >>> m.group(0) '11/27/2012' >>> m.group(1) '11' >>> m.group(2) '27' >>> m.group(3) '2012' >>> m.groups() ('11', '27', '2012') >>> month, day, year = m.groups() >>> >>> # Find all matches (notice splitting into tuples) >>> text 'Today is 11/27/2012. PyCon starts 3/13/2013.' >>> datepat.findall(text) [('11', '27', '2012'), ('3', '13', '2013')] >>> for month, day, year in datepat.findall(text): ... print('{}-{}-{}'.format(year, month, day)) ... 2012-11-27 2013-3-13 >>> ``findall()`` 方法会搜索文本并以列表形式返回所有的匹配。 如果你想以迭代方式返回匹配,可以使用 ``finditer()`` 方法来代替,比如: .. code-block:: python >>> for m in datepat.finditer(text): ... print(m.groups()) ... ('11', '27', '2012') ('3', '13', '2013') >>> ---------- 讨论 ---------- 关于正则表达式理论的教程已经超出了本书的范围。 不过,这一节阐述了使用re模块进行匹配和搜索文本的最基本方法。 核心步骤就是先使用 ``re.compile()`` 编译正则表达式字符串, 然后使用 ``match()`` , ``findall()`` 或者 ``finditer()`` 等方法。 当写正则式字符串的时候,相对普遍的做法是使用原始字符串比如 ``r'(\d+)/(\d+)/(\d+)'`` 。 这种字符串将不去解析反斜杠,这在正则表达式中是很有用的。 如果不这样做的话,你必须使用两个反斜杠,类似 ``'(\\d+)/(\\d+)/(\\d+)'`` 。 需要注意的是 ``match()`` 方法仅仅检查字符串的开始部分。它的匹配结果有可能并不是你期望的那样。比如: .. code-block:: python >>> m = datepat.match('11/27/2012abcdef') >>> m <_sre.SRE_Match object at 0x1005d27e8> >>> m.group() '11/27/2012' >>> 如果你想精确匹配,确保你的正则表达式以$结尾,就像这么这样: .. code-block:: python >>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)$') >>> datepat.match('11/27/2012abcdef') >>> datepat.match('11/27/2012') <_sre.SRE_Match object at 0x1005d2750> >>> 最后,如果你仅仅是做一次简单的文本匹配/搜索操作的话,可以略过编译部分,直接使用 ``re`` 模块级别的函数。比如: .. code-block:: python >>> re.findall(r'(\d+)/(\d+)/(\d+)', text) [('11', '27', '2012'), ('3', '13', '2013')] >>> 但是需要注意的是,如果你打算做大量的匹配和搜索操作的话,最好先编译正则表达式,然后再重复使用它。 模块级别的函数会将最近编译过的模式缓存起来,因此并不会消耗太多的性能, 但是如果使用预编译模式的话,你将会减少查找和一些额外的处理损耗。