正则表达式使用笔记

Created: Aug 12, 2020 11:28 AM Tags: Python, RegExp, re, regex

re 模块
import re

编译标志

ASCII (A)

使几个转义如 \w、\b、\s 和 \d 匹配仅与具有相应特征属性的 ASCII 字符匹配。

DOTALL (S)

使 . 匹配任何字符,包括换行符

IGNORECASE (I)

进行大小写不敏感匹配

re.search('AbC', 'aBc', re.I)
# <re.Match object; span=(0, 3), match='aBc'>

LOCALE (L)

进行区域设置感知匹配

MULTILINE (M)

多行匹配,影响 ^ 和 $

VERBOSE (X)

启用详细的正则,可以更清晰,更容易理解。

转义字符

反斜杠 \

可以转义已有的通配符,保留原本的字符匹配含义

re.compile("\\section").search("aaa\section")
# None

re.compile(r"\\d").search("aaa\dbbb")
# <re.Match object; span=(3, 5), match='\\d'>

通用匹配符

.

  • 匹配单个字符
re.search('.', 'abc')
# <re.Match object; span=(0, 1), match='a'>

+

匹配前一个符号 1 至无穷次

re.search('a+', 'aaab')
# <re.Match object; span=(0, 3), match='aaa'>

*

匹配前一个符号 0 至无穷次

re.search('ca*b', 'cb')
<re.Match object; span=(0, 2), match='cb'>

?

匹配前一个符号 0 次或 1次

re.search('ca?b', 'caab')
# None

{m,n}

匹配前一个符号 m 至 n 次

re.search('c2{4}b', 'c2222b')
# <re.Match object; span=(0, 6), match='c2222b'>

[ ]

匹配字符集

re.search('c[A-Z]b', 'cZb')
# <re.Match object; span=(0, 3), match='cZb'>

^

匹配行首

re.search('^cb', 'acb')
# None

否定

re.search('[^ab]', 'acb')
# <re.Match object; span=(1, 2), match='c'>

$

匹配行尾

re.search('c$', 'abc')
# <re.Match object; span=(2, 3), match='c'>

|

分割不同的匹配正则

re.search('d$|^a', 'abc')
# <re.Match object; span=(0, 1), match='a'>

\d

匹配十进制数字,等价于 [0-9]

re.search('\d+', 'ac12345v')
# <re.Match object; span=(2, 7), match='12345'>

补集 **\D**,等价于 [^0-9]

\w

匹配字母以下划线及数字,等价于 [a-zA-Z0-9_]

re.search('\w+', 'ac1v_c%3')
# <re.Match object; span=(0, 6), match='ac1v_c'>

补集 **\W**,等价于 [^a-zA-Z0-9_]

\s

匹配空格,等价于 [ \t\n\r\f\v]

re.search('\s+', 's \t p ace')
# <re.Match object; span=(1, 4), match=' \t '>

补集 **\S**,等价于 [^ \t\n\r\f\v]

贪婪/非贪婪

正则表达式默认会尽可能多的匹配(贪婪)

re.findall('\d+', 'a1234455b')
# ['1234455']

更少的匹配需要在贪婪算符后增加 ?

+? *? ?? {m,n}?

re.findall('\d+?', 'a1234455b')
# ['1', '2', '3', '4', '4', '5', '5']

回溯灾难

如果在正则内连用两个贪婪匹配算符,可能会导致回溯灾难(Catastrophic Backtracking)

# !!!!!!!!!!!!!!! 谨慎运行 !!!!!!!!!!!!!!!!!!
re.search("(a+)+b", "aaaaaaaaaaaaaaaaaaaaa")
# 会循环尝试所有的组合 (a+)+ 的组合,导致卡死,大概有 O(2^N) 组合

from functools import partial
import timeit

def a(i):
    return i, re.search("(a+)+b", "a"*i)

for i in range(15, 25):
    print(timeit.timeit(partial(a, i), number=10))

# 0.0691330000000221
# 0.11274059999999508
# 0.16568009999997457
# 0.3310396999999625
# 0.6679392000000348
# 1.4523080000000164
# 2.7366319000000203
# 6.06315950000004
# 10.824507399999959
# 23.64085460000001

捕获匹配组

捕获分组 (regex)

re.findall("(\d+)", "abc123def234")
# ['123', '234']

re.search("(\d+)", "abc123def234")
# <re.Match object; span=(3, 6), match='123'>

分组不捕获 (?:regex)

re.search("(?:\D+)-(\d+)", "abc-234").groups()
# ('234',)

分组配置 (?[aiLmsux]:regex)

re.findall("(?i:[a-z]+)", "abc123DEF234")
# ['abc', 'DEF']

前项肯定 (?<=regex)

re.search(r"(?<=abc)\d+", "abc123deb").group()

后项肯定 (?=regex)

re.search(r"(\d+(?=abc))", "xbc123abc").group()

前项否定 (?<!regex)

re.search(r"(?<!xbc)\d+", "abc123abc").group()

后项否定 (?!regex)

re.search(r"\d+(?!xbc)", "123abc").group()

模块常用方法

re.compile

预编译正则的方法,可以加速程序运行的时间

number_regex = re.compile('[0-9]+')

re.escape

把已有的字符串做转义

re.escape('\d')
# '\\\\d'

re.search(re.escape('\d[0-9]'), '121\d[0-9]abc')
# <re.Match object; span=(3, 10), match='\\d[0-9]'>

re.findall

查找所有能匹配上的子字符串

re.findall('a+', 'aaaa')
# ['aaaa']

re.findall('a+?', 'aaaa')
# ['a', 'a', 'a', 'a']

re.match

匹配后的第一个对象

matched = re.match('a+?', 'aaaa')
# <re.Match object; span=(0, 1), match='a'>

matched.span()
# (0, 1)

matched.group()
# 'a'

re.search

搜索并返回匹配对象

searched= re.search('(a)(b)', 'aaaab')
# <re.Match object; span=(3, 5), match='ab'>

searched.groups()
# ('a', 'b')

re.split

按照 regex pattern 拆分字符

re.split('\s', 'This is a test string')
# ['This', 'is', 'a', 'test', 'string']

re.sub

替换正则匹配的文字

可以用 \1 \2 在替换的文本内表示正则的匹配内容,依次为所有捕获的匹配组

re.sub('_', ' ', 'This_is_a_test_string')
# 'This is a test string'

re.sub('([a-z]+)(\d+)', r'\2\1', 'abc123xyz987')
'123abc987xyz'