Python 中一类主要的处理对象就是字符串,如何从字符串中筛选特定模式的子字符串就需要正则表达式。使用正则表达式的模式和字符串既可以是 Unicode 字符串(str),也可以是 8 为字节串(bytes)。本节介绍如何在 Python 中使用正则表达式。

正则表达式调用格式

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

re_exp = re.compile(r'xxx', flags=flag)
re_exp.match(str_instance)
re_exp.findall(str_instance)
re_exp.search(str_instance)
re_exp.split(str_instance)

# or (除了上面编译后在匹配,还可以采用下面的合成形式,实际上内部是采用上面的步骤)
re.match(r'xxx', str_instance, flags=flag)
re.findall(r'xxx', str_instance, flags=flag)
...

其中,re 是 python 内置的正则表达式模块,r’xxx’ 为匹配模式,flags 为一个可选参数,称为标记,可配置为是否忽略大小写(re.I)、采用多行模式(re.M)等。re.compile 为将匹配模式编译为正则表达式,会被缓存。match, findall 等编译后的正则表达式对象支持的方法和属性(如 re_exp.flags, re_exp.groups, re_exp.pattern等)。

正则表达式语法-匹配模式

正则表达式中有一些特殊语法,具体的如下:

  1. 绝大部分普通字符(如 ‘A’, ‘a’, ‘0’ 等)是最简单的表达式,匹配自身;除此之外,还有一些特殊字符。
  2. . 默认模式下匹配除了换行以外的任意字符;如 py. 可匹配 ‘pyc’, ‘pyo’, ‘py!’
  3. ^ 匹配字符串的开头;
  4. $ 匹配字符串的结尾;
  5. * 对它前面的正则式匹配 0 到任意次重复,尽量多的匹配字符串,即贪婪匹配;
  6. + 对它前面的正则式匹配 1 到任意次重复;
  7. ? 对它前面的正则式匹配 0 到 1 次重复;
  8. *?, +?, ?? 因为 *, +, ? 都是贪婪匹配,它们会进行尽可能多的匹配。但有时不需要这种行为,在它们末尾再增加 ? 则转变为非贪婪方式,会尽量少的进行字符匹配;
  9. {m} 对其前面的正则式指定匹配 $m$ 个重复,如果少于 $m$ 个则会导致匹配失败;
  10. {m,n} 对其前面的正则式进行 $m$ 到 $n$ 次匹配,属于贪婪式,会在 $m$ 和 $n$ 之间取尽量多。忽略 $m$ 意指 $m=0$,忽略 $n$ 意指 $n$ 取无穷大。特别注意逗号不能省略,否则无法辨识修饰符应该忽略哪个边界;
  11. {m,n}? 采用非贪婪模式,只匹配 $m$ 和 $n$ 之间尽量少的字符次数;
  12. \ 转义字符(允许你匹配上面出现的特殊字符);
  13. [] 表示在中括号里的字符为字符串中出现的可选字符。只匹配中括号中出现的字符。如 [a-f0-9A-F] 匹配十六进制位、[0-5][0-9]匹配 00 到 59 时间数。如果特殊字符出现在中括号中,则将使特殊字符失去特殊含义,功能同转义字符。[^5] 匹配除了5以外的字符;如果要匹配 ],可以采用:1)前面增加转义字符 [()\]] ;2)放在首位 []()],其中 () 为其他需要匹配的字符;
  14. | 或符号,如 A|B 则正则式 AB
  15. () 表示将匹配完成的内容截取除小括号中的内容;
  16. \A 只匹配字符串开始;
  17. \b 匹配空字符串,但只能在单词开始或结尾的位置;
  18. \B 匹配空字符串,但不能在单词的开始或结尾的位置,如 r'py\B' 匹配 ‘python’, ‘py3’,但不匹配 ‘py’;
  19. \d 匹配十进制数;如 00\d 可匹配 ‘007’,但无法匹配 ‘00A’
  20. \D 匹配非十进制数;
  21. \s 匹配空白字符;
  22. \S 匹配非空白字符;
  23. \w 匹配词语字符,包含构成词语的绝对部分字符,也包括数字和下划线;如 \w\w\d 可匹配 ‘py3’
  24. \W 匹配非单词字符的字符;
  25. \Z 只匹配字符串尾;

正则表达式 flags 标记

  1. re.Are.ASCII\w, \W, \b, \B, \d, \D, \s, \S 只匹配 ASCII,而不是 Unicode;
  2. re.DEBUG 显示编译时的 debug 信息;
  3. re.Ire.IGNORECASE 忽略大小写匹配;如模式 [A-Z] 也只会匹配小写字符;
  4. re.Lre.LOCALE 由当前语言区域决定 \w, \W, \b, \B 和大小写敏感匹配;
  5. re.Mre.MULTILINE 样式字符 ^ 匹配字符串的开始,和每一行的开始(换行符后面紧跟的符号)、样式字符 $ 匹配字符串尾,和每一行的结尾(换行符前面那个符号)。注意,默认情况下 ^ 只匹配字符串开头,$ 只匹配字符串结尾;
  6. re.Sre.DOTALL. 匹配换行符,默认情况下,. 只匹配除了换行符以外的其他任意字符;
  7. re.Xre.VERBOSE 这个标记允许你编写更具可读性更友好的正则表达式。通过分段和添加注释。

正则表达式函数

  1. re.search(pattern, string, flags=0) 扫描整个 字符串 string 找到匹配样式的第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回一个 None ; 注意这和找到一个零长度匹配是不同的;
  2. re.match(pattern, string, flags=0) 只从 string 的开始匹配到了正则表达式样式,就返回一个相应的匹配对象 。 如果没有匹配,就返回 None ;注意它跟零长度匹配是不同的。注意即便是 MULTILINE多行模式, re.match()也只匹配字符串的开始位置,而不匹配每行开始。如果你想定位 string 的任何位置,使用 search() 来替代;
  3. re.fullmatch(pattern, string, flags=0) 如果整个 string 匹配到正则表达式样式,就返回一个相应的 匹配对象 。 否则就返回一个 None ;注意这跟零长度匹配是不同的;
  4. re.split(pattern, string, maxsplit=0, flags=0)pattern 分开 string。如果在 pattern 中捕获到括号,那么所有的组里的文字也会包含在列表里。如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素;
  5. re.findall(pattern, string, flags=0) 以列表形式返回所有匹配到的子字符串;
  6. re.finditer(pattern, string, flags=0) patternstring 里所有的非重复匹配,返回为一个迭代器 iterator 保存了匹配对象。 string 从左到右扫描,匹配按顺序排列。空匹配也包含在结果里;
  7. re.sub(pattern, repl, string, count=0, flags=0) 返回通过使用 repl 替换在 string 最左边非重叠出现的 pattern 而获得的字符串。 如果样式没有找到,则不加改变地返回 stringrepl 可以是字符串或函数;如为字符串,则其中任何反斜杠转义序列都会被处理;
  8. re.subn(pattern, repl, string, count=0, flags=0) 行为与 sub() 相同,但是返回一个元组 (字符串, 替换次数);
  9. re.escape(pattern) 转义 pattern 中的特殊字符。如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的;
  10. re.purge() 清除正则表达式的缓存。

示例

  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是 Python 合法的变量;

  • A|B 可以匹配 A 或 B,所以 (P|p)ython 可以匹配 ‘Python’ 或者 ‘python’;

  • ^\d表示必须以数字开头;

  • \d$表示必须以数字结束;

1
2
re.split(r'\s+', 'a b   c')
['a', 'b', 'c']
1
2
re.split(r'[\s\,]+', 'a,b, c  d')
['a', 'b', 'c', 'd']
1
2
re.split(r'[\s\,\;]+', 'a,b;; c  d')
['a', 'b', 'c', 'd']
1
2
re.findall(r"(\w+)=(\d+)", "set width=20 and height=10")
[('width', '20'), ('height', '10')]
1
2
3
4
5
6
7
8
9
10
11
12
13
# 替换,下面例子将 / 替换为 -
text='datetime:11/28/2022'
# 小括号里面的为单元项,从左到右依次为 1, 2, 3
datepat=re.compile(r'(\d+)/(\d+)/(\d+)')
# \3 表示前面的第 3 个单元项;\1 表示前面的第 1 个单元项;\2 表示前面的第 2 个单元项
print(datepat.sub(r'\3-\1-\2', text))
print(text)

datetime:2022-11-28
datetime:11/28/2022
# 删除第 2 个单元项
print(datepat.sub(r'\1/\3', text))
print(text)

参考文献

  1. 正则表达式
  2. re — 正则表达式操作