因为正则表达式的在文本处理,字符串匹配中有着重要应用,因此本文对其基本语法规则及在Python中的应用进行了简要介绍。

注:本文大部分是对博文正则表达式30分钟入门教程LiZeC的正则表达式笔记以及python re库官方文档的归纳和整理,更详细内容可查阅原文。

Examples

正则表达式 要匹配的字符串
\bhi\b 单词“hi”
不能直接用hi来匹配,否则会将him,history,high等单词也匹配上)
\bhi\b.*\bLucy\b hi后面不远处跟着一个Lucy
0\d{2}-\d{8} 以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字
(也就是中国的电话号码)
\ba\w*\b 以字母a开头的单词
^[A-Za-z]+$ 由26个字母组成的字符串
^[A-Za-z0-9]+$ 由26个字母和数字组成的字符串
^[0-9]*[1-9][0-9]*$ 正整数形式的字符串(不能全0)
(\d{1,3}\.){3}\d{1,3} IP地址表达式(应用了分组的概念)
  • 对于上述IP地址匹配,一个缺点是将256.300.888.999这种不可能存在的IP地址也匹配了。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
  • 0\d{2}-\d{8}|0\d{3}-\d{7}:这个表达式能匹配两种以连字号分隔的电话号码(应用了分支条件):
    一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)|

基本语法

  • 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),主要用来匹配特定的字符串。
  • 正则表达式中分为普通字符和特殊字符,普通字符表示匹配和其相同的字符;特殊字符并不匹配他们本身,而是有特殊含义的,具体有:. ^ $ * + ? { } [ ] \ | ( ),除了这些特殊字符外都是普通字符。
  • 若要匹配这些字符本身,则需要加上\进行转义(escape),如\*表示匹配*本身。
  • 因为python字符串也是使用\作为转义字符,因此在python中使用正则表达式需要再加一个反斜杠,即用\\表示转义;也可以用原声字符串的形式,即在字符串前面加,如r"\w"

一些特殊字符含义

    字符     含义
. 匹配任意单个字符(除换行符“\n”外)
() 指定分组
[] 指定字符类
{} 指定重复次数
\s 匹配任意的空白符,包括包括空格,制表符(Tab),换行符,中文全角空格等
\w 匹配任意字母、数字、下划线、汉字(即匹配普通字符)
\s 匹配任意的空白符(空格、制表符(Tab)、换行符、中文全角空格等)
\d 匹配数字(0-9)
\b 检测单词的开始或结束(单词边界),如空格,标点符号或者换行
但\b并不匹配这些单词分隔字符中的任何一个,它只匹配一个位置
^ 检测字符串的开始
$ 检测字符串的结束
  • 一些字符的反义表示
    \w\b这些特殊字符的大写表示相反的含义,具体如下:
    字符    含义
\W 匹配不是字母、数字、下划线、汉字的字符(多用来匹配特殊字符)
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置

关于\b的一些说明

  • 注:\b, ^, $匹配的并不是字符,而是一个位置
    如对于\blove\b,用来匹配句子”I love youlove “,其匹配出来的只有第一个love,第二个love不会匹配;其次其只匹配第一个love这个单词本身,而不包括其两端的空格(因为\b只只是检测单词边界,如空格,标点符号或者换行,而并不匹配这些字符)。
  • 所以如果想要检测单词边界,同时匹配空白符,则需要再指定\s
  • eg.匹配连续出现三次的单词,如果中间不加\s就匹配不出来(\b只匹配位置,不匹配任何字符);同时前后最好用\b来指定,因为这个单词可能在一行的开始(前面无任何字符),或者最后一个单词后面接了\n等情况。
    1
    repeat_elements = re.findall(r"\b(\w+)\b\s\1\b\s\1\b", "123 mention mention mention 321 123")

重复匹配限定符

以下字符称为“限定符”(限制次数的符号),跟在特殊字符的后面,表示重复该字符特定次数。
\d+匹配1个或更多个的数字。

限定符 含义
* 重复\(\geq\)0次
+ 重复\(\geq\)1次
? 重复0次或1次
{n} 重复n次
{n,} 重复\(\geq\)n次
{n,m} 重复n到m次

贪婪、懒惰匹配原则

  • 正则表达式的匹配原则为贪婪匹配:即在满足条件的情况下,匹配尽可能多的字符。
    eg. a.*b会匹配以a开始,以b结束的最长的字符串。如果用来搜索aabab,则会匹配整个字符串aabab而非aab
  • 若需要懒惰匹配:即匹配尽可能少的字符,就在相应的重复字符?即可。
    eg. a.*?b匹配以a开始,以b结束的最短的字符串,搜索aabab会匹配到aabab两个子串(之所以没有仅仅匹配到ab子串,是因为正则表达式的匹配还会考虑开始的先后顺序,最开始匹配的优先级最高)。
  • 懒惰匹配限定符如下:
限定符 含义
*? 重复\(\geq\)0次,但尽可能少重复
+? 重复\(\geq\)1次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,}? 重复\(\geq\)n次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复

字符类

  • [] 中的若干字符构成一个字符类(character class)。
  • 字符类是为了匹配某种字符集合,表示此位置可以匹配这个类中的任意一个字符。
  • 整个字符类所起到的作用和普通字符相同,都是只匹配单个字符,因此字符类可作为整体再接受其他限定,如? + {n,m}等等。
  • 可以使用-来表示一个范围,例如[a-c]表示[abc]
  • 在字符类中的特殊符号不被转义
  • 反向匹配:在字符类中,如果以^开头,则表示匹配除此字符类中提及的任何其他字符。如[^5]匹配任何不是5的字符。

分枝条件

  • 在正则表达式表示“或”的逻辑,两个条件用|连接即可。
    eg. \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。
  • 在使用分枝条件时要注意各个条件的顺序,如果上文改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。因为一旦前面的分枝满足的话就不会再管其他条件了。

注释

不推荐在正则表达式内部写注视,注释推荐写在正则表达式的外部语言中。

正则表达式高级特性

分组

  • 上文提到了如何重复单个字符(直接在字符后面加表示重复的限定符即可),但若想重复多个字符,就需要用到分组的概念。
  • 用小括号()来指定分组(也叫子表达式),之后就可以对这个分组的整体进行重复或其他操作。
    eg. (\d{1,3}\.){3}\d{1,3},就是一个比较粗糙的IP地址匹配式。

后向引用

  • 使用小括号指定一个分组后,这个分组可以作为一个整体在后文中作进一步处理。
  • 默认情况下,每个组会有一个组号,从左到右,第一个出现的分组组号为1(注意不是0),第二个为2,以此类推。
  • 如何引用:在后问中使用\+组号的形式来引用,如\1表示引用1号分组的。
    eg. \b(\w+)\b\s+\1\b可以来匹配如go go 或kitty kitty 这种连着两个重复单词。

零宽断言

  • 用于查找在某些内容前面或后面的东西(但不包括这些内容),类似于^ $ \b这种占位符,用于指定一个位置,这个位置应该满足一定的条件(即断言),因此被称为零宽断言。
  • (?=exp):用于匹配exp前面出现的表达式。
    e.g \b\w+(?=ing\b)用于匹配以ing为结尾的单词的前面部分(不包括ing)
  • (?<=exp):用于匹配exp后面出现的表达式。
    e.g (?<=\bre)\w+\b用于匹配以re开头的单词的后面部分(不包括re)
  • 更多示例:
    (?<=\s)\d+(?=\s):匹配以空白符间隔的数字(不包括这些空白符)
    ((?<=\d)\d{3})+\b:要给一个很长的数字中每三位间加一个逗号(当然是从右边加起了),你可以这样查找需要在前面和里面添加逗号的部分(用它对1234567890进行查找时结果是234567890

负向零宽断言

  • 用于确保某个模式不会出现。与字符类[^exp]的区别:虽然不匹配这个字符,但字符类总是会匹配某个字符的,这会限制字符类的应用场景;而负向零宽断言不匹配字符,其只指代一个位置。
    eg. 如果用\b\w*q[^u]\w*\b来匹配“出现了字母q,但是q后面跟的不是字母u”的单词,则像“Iraq, Benq”这种q直接作为最后一个字符的情况就会出错(字符类总要匹配一个字符),因此就需要负向零宽断言。
  • (?!exp):断言此位置的后面不能匹配表达式exp。
    eg.\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词
  • (?<!exp):断言此位置的前面不能匹配表达式exp。
    eg. (?<![a-z])\d{7}匹配前面不是小写字母的七位数字。

递归匹配

  • 用于匹配一些嵌套的层次结构,如(100*(50+15)),如果只是简单地使用\(.+\)则只会匹配到最左边的左括号和最右边的右括号之间的内容。假如原来的字符串里的左括号和右括号出现的次数不相等,比如(5/(3+2))),那我们的匹配结果里两者的个数也不会相等。如果想要想匹配到最长的,而且配对正确的字符串,就需要用到递归匹配。
  • 具体内容参见博文正则表达式30分钟入门教程

在Python中使用正则表达式

example code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查找字符串中连续出现三次的子串(中间以空格分隔)
list_result = re.findall(r"\b(\w+)\b\s\1\b\s\1\b",
"123 mention mention mention 123 entity entity entity")
print(list_result)
# ['mention', 'entity']

match_result = re.finditer(r"\b(\w+)\b\s\1\b\s\1\b",
"123 mention mention mention 123 entity entity entity")
if match_result == None:
print("no repeat elements")
else:
for match in match_result:
print(match.group(), "start:", str(match.start())+",",
"end:", str(match.end()))
# mention mention mention start: 4, end: 27
# entity entity entity start: 32, end: 52

m = re.findall(r"(\w+) (\w+)", "Isaac Newton, physicist")
print(m)
# [('Isaac', 'Newton')],包含多个子分组的情况下会以tuple的形式返回

使用原生字符串

  • 正则表达式中使用\n表示转义,而python中恰好也使用\n表示转义,因此在python中使用正则表达式则需要\\来表示反斜杠。
  • 为了节省过多的反斜杠,可以使用Python原生字符串特性,即在字符串开头加上r,如r\d,在这个字符串中每个字符表示其本身,Python不进行转义。

re库

在Python中使用正则表达式直接导入re库即可 import re

常用函数介绍

  • 只是简单检测字符串是否存在,使用findall()函数即可;
    否则使用finditer(),返回的match对象方便进行之后的操作;
    替换、分隔操作直接使用split()、sub()函数;
函数(常用性排序从上至下) 功能
re.findall() 在字符串中查找所有满足条件的子串,返回string list
re.finditer() 在字符串中查找所有满足条件的子串,返回匹配出的macth对象的iterator
re.split() 将正则表达式作为separator来分割字符串,返回string list
re.sub() 在字符串中替换所有匹配正则表达式的子串,返回替换后的字符串
re.search() 在字符串中查找匹配的子串第一次出现的位置,匹配成功返回match对象,否则返回None
re.match() 从字符串的起始位置开始匹配,即匹配字符串的开头是否和正则表达式匹配,返回match对象或None
re.fullmatch() 匹配整个字符串是否和正则表达式匹配,返回match对象或None
re.escape() 自动在字符串中为特殊字符添加 “\” 转义符,返回修改后的字符串
具体见下文解释

参数说明:

  • re.findall(pattern, string, flags=0)
    pattern: 表示正则表达式的字符串,推荐使用python原生字符串r"string"
    flags: 正则表达式的控制标记
    return: 返回string list,list中每一项表示匹配出来的字符串
  • re.sub(pattern, repl, string, count=0, flags=0)
    repl: 用来替代匹配到的子串的字符串
  • re.search(pattern, string, flags=0)
    参数同上
    return: 匹配成功返回match对象,否则返回None
  • re.match(pattern, string, flags=0)
    参数同上
    return:匹配成功返回match对象,否则返回None
  • re.fullmatch(pattern, string, flags=0)
    参数同上
    return:匹配成功返回match对象,否则返回Non
  • re.escape(pattern)
    eg. pattern1 = re.escape('python.exe')),pattern1就为”python\.exe”,可直接作为pattern参数传入其他函数中,用来匹配“python.exe”。当pattern含有大量特殊字符时使用这个函数就很方便。

Match类

re.search()和re.match()两个函数的返回对象,包含了匹配的相关信息,常用函数和属性如下:

名称 说明
match.group() 返回特定的分组,string或tuple的形式
match.start() 返回特定分组的起始地址
match.end() 返回特定分组的结束地址
(会比实际大1位,因为python字符串截取前闭后开的特性)
match.string 为传入到re.search()或re.match()的待匹配字符串
match.groups() 以tuple的形式返回所有分组,element即为各个分组
  • match.group([group1(int), …])
    [group1](可选):需要返回的组号;没有传参时组号默认为0,对应整个匹配到的字符串,通常情况不用传参数即可。
    这里的分组指的就是在正则表达式中通过小括号()指定的分组,每个分组从左到右会有一个组号,且组号从1开始。
    return: string字符串
  • match.start([group])、match.end([group])
    两个函数中group都是指组号,返回特定组号的起始、终止地址,默认即指整个匹配的字符串,见小节开头的例子;
  • 分组的对应子串可通过match.string[m.start(g):m.end(g)]来获取。

控制标记flag介绍

用于规定正则表达式的一些匹配规则,作为flags参数传入正则表达式函数
|标记名(简写/全称)|含义|
|:-:|:-|
|re.A / re.ASCII|使\w,\b,\s和\d只匹配ASCII字符
eg. 不会匹配汉字和其他Unicode字符|
|re.I / re.IGNORECASE|忽略正则表达式大小写|
|re.M / re.MULTILINE|使得^表示每一行的开始,$表示每一行的结束
原义仅表示一个单词的开始和结束|
|re.S / re.DOTALL|使得.可以匹配\n字符|
|re.X / re.VERBOSE|忽略正则表达式内部的空白符|
注:这些变量在VSCode的python下没有提示,但可以运行。

str.replace()和re.sub的比较

  • str.replace(old, new[, count])是字符串的替代函数,其中new为替换的字符串,old为待替换的字符串,old只能为substring而不能为字符串,因此替换功能较为简单。
  • re.sub(pattern, repl, string, count=0, flags=0)则使用了正则表达式,可进行更复杂的替换操作,但同时开销也更大。
  • 因此能用replace()尽量用,复杂的替换操作再使用正则表达式。

Post Date: 2019-02-07

版权声明: 本文为原创文章,转载请注明出处