• 2.4 字符串匹配和搜索
    • 问题
    • 解决方案
    • 讨论

    2.4 字符串匹配和搜索

    问题

    你想匹配或者搜索特定模式的文本

    解决方案

    如果你想匹配的是字面字符串,那么你通常只需要调用基本字符串方法就行,比如 str.find() , str.endswith() , str.startswith() 或者类似的方法:

    1. >>> text = 'yeah, but no, but yeah, but no, but yeah'
    2. >>> # Exact match
    3. >>> text == 'yeah'
    4. False
    5. >>> # Match at start or end
    6. >>> text.startswith('yeah')
    7. True
    8. >>> text.endswith('no')
    9. False
    10. >>> # Search for the location of the first occurrence
    11. >>> text.find('no')
    12. 10
    13. >>>

    对于复杂的匹配需要使用正则表达式和 re 模块。为了解释正则表达式的基本原理,假设你想匹配数字格式的日期字符串比如 11/27/2012 ,你可以这样做:

    1. >>> text1 = '11/27/2012'
    2. >>> text2 = 'Nov 27, 2012'
    3. >>>
    4. >>> import re
    5. >>> # Simple matching: \d+ means match one or more digits
    6. >>> if re.match(r'\d+/\d+/\d+', text1):
    7. ... print('yes')
    8. ... else:
    9. ... print('no')
    10. ...
    11. yes
    12. >>> if re.match(r'\d+/\d+/\d+', text2):
    13. ... print('yes')
    14. ... else:
    15. ... print('no')
    16. ...
    17. no
    18. >>>

    如果你想使用同一个模式去做多次匹配,你应该先将模式字符串预编译为模式对象。比如:

    1. >>> datepat = re.compile(r'\d+/\d+/\d+')
    2. >>> if datepat.match(text1):
    3. ... print('yes')
    4. ... else:
    5. ... print('no')
    6. ...
    7. yes
    8. >>> if datepat.match(text2):
    9. ... print('yes')
    10. ... else:
    11. ... print('no')
    12. ...
    13. no
    14. >>>

    match() 总是从字符串开始去匹配,如果你想查找字符串任意部分的模式出现位置,使用 findall() 方法去代替。比如:

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

    在定义正则式的时候,通常会利用括号去捕获分组。比如:

    1. >>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
    2. >>>

    捕获分组可以使得后面的处理更加简单,因为可以分别将每个组的内容提取出来。比如:

    1. >>> m = datepat.match('11/27/2012')
    2. >>> m
    3. <_sre.SRE_Match object at 0x1005d2750>
    4. >>> # Extract the contents of each group
    5. >>> m.group(0)
    6. '11/27/2012'
    7. >>> m.group(1)
    8. '11'
    9. >>> m.group(2)
    10. '27'
    11. >>> m.group(3)
    12. '2012'
    13. >>> m.groups()
    14. ('11', '27', '2012')
    15. >>> month, day, year = m.groups()
    16. >>>
    17. >>> # Find all matches (notice splitting into tuples)
    18. >>> text
    19. 'Today is 11/27/2012. PyCon starts 3/13/2013.'
    20. >>> datepat.findall(text)
    21. [('11', '27', '2012'), ('3', '13', '2013')]
    22. >>> for month, day, year in datepat.findall(text):
    23. ... print('{}-{}-{}'.format(year, month, day))
    24. ...
    25. 2012-11-27
    26. 2013-3-13
    27. >>>

    findall() 方法会搜索文本并以列表形式返回所有的匹配。如果你想以迭代方式返回匹配,可以使用 finditer() 方法来代替,比如:

    1. >>> for m in datepat.finditer(text):
    2. ... print(m.groups())
    3. ...
    4. ('11', '27', '2012')
    5. ('3', '13', '2013')
    6. >>>

    讨论

    关于正则表达式理论的教程已经超出了本书的范围。不过,这一节阐述了使用re模块进行匹配和搜索文本的最基本方法。核心步骤就是先使用 re.compile() 编译正则表达式字符串,然后使用 match() , findall() 或者 finditer() 等方法。

    当写正则式字符串的时候,相对普遍的做法是使用原始字符串比如 r'(\d+)/(\d+)/(\d+)' 。这种字符串将不去解析反斜杠,这在正则表达式中是很有用的。如果不这样做的话,你必须使用两个反斜杠,类似 '(\d+)/(\d+)/(\d+)'

    需要注意的是 match() 方法仅仅检查字符串的开始部分。它的匹配结果有可能并不是你期望的那样。比如:

    1. >>> m = datepat.match('11/27/2012abcdef')
    2. >>> m
    3. <_sre.SRE_Match object at 0x1005d27e8>
    4. >>> m.group()
    5. '11/27/2012'
    6. >>>

    如果你想精确匹配,确保你的正则表达式以- 结尾,就像这么这样:

    1. >>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)- ')
    2. >>> datepat.match('11/27/2012abcdef')
    3. >>> datepat.match('11/27/2012')
    4. <_sre.SRE_Match object at 0x1005d2750>
    5. >>>

    最后,如果你仅仅是做一次简单的文本匹配/搜索操作的话,可以略过编译部分,直接使用 re 模块级别的函数。比如:

    1. >>> re.findall(r'(\d+)/(\d+)/(\d+)', text)
    2. [('11', '27', '2012'), ('3', '13', '2013')]
    3. >>>

    但是需要注意的是,如果你打算做大量的匹配和搜索操作的话,最好先编译正则表达式,然后再重复使用它。模块级别的函数会将最近编译过的模式缓存起来,因此并不会消耗太多的性能,但是如果使用预编译模式的话,你将会减少查找和一些额外的处理损耗。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c02/p04_match_and_search_text.html