Python的正则表达式模块re

在Python中re模块提供了正则表达式的相关操作。其本质,RE是一种小型的,高度专业化的编程语言,通过re模块来实现其功能。正则表达式被编译成一系列字节码,然后由C编写的匹配引擎执行。

我们将涉及两个重要的功能,这将被用于处理的正则表达式。但是首先:有各种各样的字符,当它们在正则表达式中使用,将有特殊的意义。为了避免在处理正则表达式的任何困惑,将使用原始字符串作为r’expression“。

Python Version:3.5+

匹配

字符匹配

  • . 默认匹配除换行符以外的任意字符
  • ^ 匹配字符串的开始
  • $ 匹配字符串的结束
  • [] 指定字符范围匹配
  • | 或
  • \ 转义字符,反斜杠本身需要反斜杠去转义
  • () 分组,去已经匹配到的数据中再去提取数据
  • \d 匹配任何十进制数;它相当于类 [0-9]。
  • \D 匹配任何非数字字符;它相当于类 [^0-9]。
  • \s 匹配任何空白字符;它相当于类 [ \t\n\r\f\v]。
  • \S 匹配任何非空白字符;它相当于类 [^ \t\n\r\f\v]。
  • \w 匹配任何字母数字字符;它相当于类 [a-zA-Z0-9_]。
  • \W 匹配任何非字母数字字符;它相当于类 [^a-zA-Z0-9_]
  • \b 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。
  • \B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。

次数匹配

    • 重复0次或多次
    • 重复1次或多次
  • ? 重复0次或1次
  • {n} 重复n次
  • {n,} 重复n次或多次
  • {n, m} 重复n次到m次

修饰符

  • re.I 使匹配对大小写不敏感
  • re.L 做本地化识别(locale-aware)匹配
  • re.M 多行匹配,影响 ^ 和 $
  • re.S 使 . 匹配包括换行在内的所有字符
  • re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
  • re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

正则表达式实例

普通字符匹配

  • python 匹配 python
1
2
>>> re.findall(r"python", "Pythonpythoner", re.I)
['Python', 'python']

锚点匹配

  • ^python$ 匹配以python开头结尾的字符串
1
2
3
4
>>> re.findall(r"^python$", "Pythonpythoner", re.I)
[]
>>> re.findall(r"^python$", "Python", re.I)
['Python']

范围匹配

  • [Pp]ython 匹配 “Python” 或 “python”
  • rub[ye] 匹配 “ruby” 或 “rube”
  • [aeiou] 匹配中括号内的任意一个字母
  • [0-9] 匹配任何数字。类似于 [0123456789]
  • [a-z] 匹配任何小写字母
  • [A-Z] 匹配任何大写字母
  • [a-zA-Z0-9] 匹配任何字母及数字
  • [^aeiou] 除了aeiou字母以外的所有字符
  • [^0-9] 匹配除了数字外的字符

或匹配

  • python|java 匹配pythonjava
1
2
>>> re.findall(r"python|java", "Python and java", re.I)
['Python', 'java']

匹配反斜杠

  • \\ 匹配\
1
2
>>> re.findall(r"\\", "Python\ and java", re.I)
['\\']

注意:在匹配的使用,如果想匹配字符串中的一个反斜杠,需要在原生字符串中写两个反斜杠。在匹配结果中,re中匹配到了一个\但是返回的却是\\,是因为Python中也需要使用两个反斜杠来表示一个反斜杠

这里需要详细解释一下:Python与re之间的转义

在之前的笔记中,我们已经知道原生字符串的概念,简单来说,就是我在python中一旦在字符串前面加了r就表示后面这串字符串中所有的字符都当做普通字符来处理。在上面的例子中,我们已经使用了原生字符串的写法,声明字符串中的\已经是普通字符了,为什么到了re中,需要python给出\\才能匹配到\呢?

1
2
3
4
>>> print('\\')
\
>>> print(r'\\')
\\

这是因为,re和Python其实是两套系统,在Python中虽然明确给出了让re去匹配\,但是这一个\传递给re时,在re中一个\也是特殊字符,表示转义,所以不能正确在字符串中匹配到字符\,为了抵消掉re中\的特殊含义,需要使用\\来匹配到一个\,所以从re的角度来说,就要求Python传递过去的是\\才能让re在字符串中匹配到\

1
2
>>> re.findall("\\\\", "Python\ and java", re.I)
['\\']

对比一下,如果不使用原生字符串的话,re仍然需要Python传递过去\\才能匹配到\,但是Python中的字符串如果没有声明是原生字符串的话需要使用\\\\才能表示\\

re模块

上面简单的介绍完re的基本表达式,下面就可以实际使用re的函数来操作匹配字符串啦

match

match,从起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None

1
2
3
4
def match(pattern, string, flags=0):
"""Try to apply the pattern at the start of the string, returning
a match object, or None if no match was found."""
return _compile(pattern, flags).match(string)

参数

  • pattern 正则表达式
  • string 字符串
  • flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配对象方法

  • group() 获取匹配到的所有结果
  • groups() 获取模型中匹配到的分组结果
  • groupdict() 获取模型中匹配到的分组结果
  • start() 返回匹配开始的位置
  • end() 返回匹配结束的位置
  • span() 返回一个元组包含匹配 (开始,结束) 的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 无分组

import re

r = re.match('com', 'comwww.runcomoob')
print(r.group())
print(r.groups())
print(r.groupdict())

r = re.match('com', 'Comwww.runComoob', re.I)
print(r.group())
print(r.groups())
print(r.groupdict())

------------
com
()
{}
Com
()
{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 有分组
# 为何要有分组?提取匹配成功的指定内容(先匹配成功全部正则,再匹配成功的局部内容提取出来)

import re

r = re.match('c(..)', 'comwww.runcomoob')
print(r.group())
print(r.groups())
print(r.groupdict())

r = re.match('c(..)', 'Comwww.runComoob', re.I)
print(r.group())
print(r.groups())
print(r.groupdict())

------------
com
('om',)
{}
Com
('om',)
{}

观察取出来的结果,groups()中有了匹配结果,匹配的是group()中的结果。也就是说,使用分组,意味着在匹配到整个字符串中再次进行匹配并取值(取括号中的值)

search,浏览整个字符串去匹配第一个,未匹配成功返回None

1
2
3
4
def search(pattern, string, flags=0):
"""Scan through string looking for a match to the pattern, returning
a match object, or None if no match was found."""
return _compile(pattern, flags).search(string)

参数

  • pattern 正则表达式
  • string 字符串
  • flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配对象方法

  • group() 获取匹配到的所有结果
  • groups() 获取模型中匹配到的分组结果
  • groupdict() 获取模型中匹配到的分组结果
  • start() 返回匹配开始的位置
  • end() 返回匹配结束的位置
  • span() 返回一个元组包含匹配 (开始,结束) 的位置
1
2
3
4
5
6
7
8
9
10
11
import re

r = re.search('\dcom', 'www.4comrunoob.5com')
print(r.group())
print(r.groups())
print(r.groupdict())

------------
4com
()
{}

re.match与re.search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re

line = "Cats are smarter than dogs"

matchObj = re.match(r'dogs', line, re.M | re.I)
if matchObj:
print("match --> matchObj.group() : ", matchObj.group())
else:
print("No match!!")

matchObj = re.search(r'dogs', line, re.M | re.I)
if matchObj:
print("search --> matchObj.group() : ", matchObj.group())
else:
print("No match!!")

------------
No match!!
search --> matchObj.group() : dogs

findall

findall,找到 RE 匹配的所有子串,并把它们作为一个列表返回

1
2
3
4
5
6
7
8
9
def findall(pattern, string, flags=0):
"""Return a list of all non-overlapping matches in the string.

If one or more capturing groups are present in the pattern, return
a list of groups; this will be a list of tuples if the pattern
has more than one group.

Empty matches are included in the result."""
return _compile(pattern, flags).findall(string)

参数

  • pattern 正则表达式
  • string 字符串
  • flags 编译标志位,用于修改正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 无分组

import re

r = re.findall(r'\d+', '1one1two2three3four4')
print(r)


# 有分组
r = re.findall(r'\d+(..)', '1one1two2three3four4')
print(r)

------------
['1', '1', '2', '3', '4']
['on', 'tw', 'th', 'fo']

finditer

找到 RE 匹配的所有子串,并把它们作为一个迭代器返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import

r = re.finditer(r'\d+', '1one1two2three3four4')
print(r)
for i in r:
print(i.span(), i.group(), i.groupdict())

------------
<callable_iterator object at 0x101178748>
(0, 1) 1 {}
(4, 5) 1 {}
(8, 9) 2 {}
(14, 15) 3 {}
(19, 20) 4 {}

sub

sub,替换匹配成功的指定位置字符串

1
2
3
4
5
6
7
8
def sub(pattern, repl, string, count=0, flags=0):
"""Return the string obtained by replacing the leftmost
non-overlapping occurrences of the pattern in string by the
replacement repl. repl can be either a string or a callable;
if a string, backslash escapes in it are processed. If it is
a callable, it's passed the match object and must return
a replacement string to be used."""
return _compile(pattern, flags).sub(repl, string, count)

参数

  • pattern: 正则模型
  • repl : 要替换的字符串或可执行对象
  • string : 要匹配的字符串
  • count : 指定匹配个数
  • flags : 匹配模式
1
2
3
4
5
6
7
8
# 与分组无关
import re

r = re.sub("a\w+", "18", "hello ps bcd ps lge ps acd 25", 2)
print(r)

------------
hello ps bcd ps lge ps 18 25

split

split,根据正则匹配分割字符串

1
2
3
4
5
6
7
8
9
def split(pattern, string, maxsplit=0, flags=0):
"""Split the source string by the occurrences of the pattern,
returning a list containing the resulting substrings. If
capturing parentheses are used in pattern, then the text of all
groups in the pattern are also returned as part of the resulting
list. If maxsplit is nonzero, at most maxsplit splits occur,
and the remainder of the string is returned as the final element
of the list."""
return _compile(pattern, flags).split(string, maxsplit)
  • pattern: 正则模型
  • string : 要匹配的字符串
  • maxsplit:指定分割个数
  • flags : 匹配模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 无分组

import re

origin = "hello ps bcd ps lge ps acd 25"
r = re.split("ps", "hello ps bcd ps lge ps acd 25", 1)
print(r)
r = re.split("ps", "hello ps bcd ps lge ps acd 25", 2)
print(r)
r = re.split("ps", "hello ps bcd ps lge ps acd 25", 3)
print(r)

------------
['hello ', ' bcd ps lge ps acd 25']
['hello ', ' bcd ', ' lge ps acd 25']
['hello ', ' bcd ', ' lge ', ' acd 25']
1
2
3
4
5
6
7
8
9
10
11
12
13
# 有分组

import re

origin = "hello ps bcd ps lge ps acd 25"
r = re.split("(ps)", "hello ps bcd ps lge ps acd 25", 1)
print(r)
r = re.split("p(s)", "hello ps bcd ps lge ps acd 25", 1)
print(r)

------------
['hello ', 'ps', ' bcd ps lge ps acd 25']
['hello ', 's', ' bcd ps lge ps acd 25']

从上面的实例中可以看出,在不分组的情况下,列表的长度=maxsplit+1,匹配到的分割字符串并不会返回回来;而在分组的情况下,列表的长度=maxsplit+2,匹配到的字符串也会返回到列表中

compile

compile,编译正则表达式

1
2
3
def compile(pattern, flags=0):
"Compile a regular expression pattern, returning a pattern object."
return _compile(pattern, flags)

参数

  • pattern: 正则模型
  • flags : 匹配模式
1
2
3
4
5
6
7
import re

p = re.compile(r'\d+(...)')
print(p.findall('123one134two256three378four469'))

------------
['one', 'two', 'thr', 'fou']

先把正则表达式编译再匹配,在遍历匹配大型文件时,效率会提高很多,免去了每次还要编译正则表达式

常用正则表达式

  • IP:^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
  • 手机号:^1[3|4|5|8][0-9]\d{8}$
  • 邮箱:[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+

正则表达式计算器实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import re

# test_expression = '12+33*(11.5+22.5)*44/2+59-(2*3)+(8*5-(10/2))'


def qukuohao(s):
"""
将表达式中所有括号中的内容计算成数值,去掉表达式中所有的括号
:param s: 计算表达式[str]
:return: 没有括号的计算表达式[str]
"""
while True:
if '(' in s:
r = re.split(r'\(([^()]*)\)', s, 1)
# ret = eval(r[1])
ret = cal(r[1])
s = r[0] + str(ret) + r[2]
else:
return s


def fuhao(s):
"""
处理符号的问题
:param s: 计算表达式
:return: 计算表达式
"""
ss = s.replace('+-', '-')
sss = ss.replace('--', '+')
return sss


def chengshufajisuan(s):
"""
专门用来计算不带括号的计算表达式中的乘除法
:param s: 不带括号的计算表达式
:return: 只有加减法的计算表达式(如果不再有加减法,直接返回计算结果)
"""
while True:
if '*' in s or '/' in s:
r = re.split(r'(\d+\.?\d*[*|/]-?\d+\.?\d*)', s, 1)
ss = eval(r[1])
s = r[0] + str(ss) + r[2]
else:
break
return s


def jiajianfajisuan(s):
"""
专门用来计算不带括号的计算表示式中的加减法
:param s: 不带括号且只有加减法的表达式
:return: 最终的计算结果
"""
s = fuhao(s)
while True:
# 5-9的情况;-5-9的情况都需要额外考虑
if '+' in s or s.count('-') > 1 or (s.count('-') == 1 and not s.startswith('-')):
r = re.split(r'(-?\d+\.?\d*[+|-]\d+\.?\d*)', s, 1)
ss = eval(r[1])
s = r[0]+str(ss)+r[2]
else:
break
return s


def cal(s):
"""
+ - * / 混合运算
:param s:
:return:
"""
if '*' in s or '/' in s:
s = chengshufajisuan(s)
if '+' in s or s.count('-') > 1:
s = jiajianfajisuan(s)
return s
else:
return s
elif '+' in s or s.count('-') > 1 or (s.count('-') == 1 and not s.startswith('-')):
s = jiajianfajisuan(s)
return s
else:
return None

# print(cal(qukuohao(test_expression)))

if __name__ == '__main__':
try:
while True:
e = input('输入计算表达式> ')
if '(' in e:
print(cal(qukuohao(e)))
else:
print(cal(e))
except KeyboardInterrupt:
print('Bye')

参考文档:

在线正则匹配工具: