速通正则表达式(Regular Expression)
1. 介绍
在日常工作中,无论使用何种编程语言,甚至于不使用编程语言,使用正则表达式筛选数据是非常常见的场景
正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。
本文会以最简单的方式介绍所有正则表达式的例子,适合快速入门与定时回顾,本篇分为正则表达式的语法与Python re模块三个方法的介绍
2. 语法2.1. 基础语法
2.1.1. 元字符
元字符是正则表达式最基础的构成,举一个最简单的例子
- 匹配字符串中的world单词:hello world
- 使用
\bworld\b
匹配得到:world
其中\b就是元字符之一,元字符的列表如下
代码 | 说明 |
---|---|
. | 匹配除了换行符以外的任意字符 |
\w | 匹配字母/数字/下划线/汉字 |
\s | 匹配任意空白符 |
\d | 匹配任意数字 |
\b | 匹配单词的开始/结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
[abc] | 匹配abc中任意一个字符,abc也可换成其他特定字符 |
熟悉上面的元字符语法之后,尝试用正则表达式来匹配一下下面几个问题
- 匹配电话号码:My phone number is 020-09201928
- 匹配单词首字母:Nor is there any clear sense of what the endgame might be.
- 匹配句中的日语等级(N1/N2..):我的日语等级是N3,你呢?
问题答案
- \b\d\d\d-\d\d\d\d\d\d\d\d (or. \b\d{3}-\d{8} )
- \b\w
- [N]\d
2.1.2. 反义
对于上面的基础字符,\d的意思是匹配任意数字,如果我们需要匹配“任意非数字”呢?那就需要反义(字符)了
代码 | 说明 |
---|---|
\W | 匹配所有不是字母/数字/下划线/汉字的字符,等价于[^A-Za-z0-9_] |
\S | 匹配所有不是空白符的条件,等价于[^ \f\n\r\t\v] |
\D | 匹配所有非数字的字符,等价于[^0-9] |
\B | 匹配不是单词开头或结束的位置 |
[^abc] | 匹配除了abc以外的所有字符 |
照例,试着解决以下问题
- 匹配very中的er:very never
- 匹配首字母除了t外的所有单词:Nor is there any clear sense of what the endgame might be.
问题答案
- er\B
- \b[^t\s].*?\b
2.1.3. 重复与转义
元字符列表除了上述的匹配
代码 | 说明 |
---|---|
* | 重复0次或者多次 |
+ | 重复1次或者多次 |
? | 重复0次或者1次 |
\ | 转义符,如想匹配问号,则是'\?' |
{n} | 重复N次 |
{n,} | 重复N次或者更多次 |
{n,m} | 重复N次或者M次 |
问题
- 匹配以w开头或者y开头的单词:I don't give a brass farthing for what you think, I'm going to do it anyway.
- 匹配问号前的单词:Can you turn the declarative sentence into a rhetorical question?
- 匹配句中长度小于5(不含5)的单词:The key verse is a rhetorical question meaning "yes, if you do well (by obeying the Lord), you will indeed be accepted . "
答案
- \b[wy].*?\b
- \b\w+?
- \b\w{1,4}\b
2.1.4. 范围匹配
除去上面的基础字符,还有一些常用的范围匹配写法,如下
代码 | 说明 |
---|---|
[a-z] | 匹配“a”到“z”范围内的任意小写字母字符 |
[A-Z] | 匹配“A”到“Z”范围内的任意大写字母字符 |
[0-9] | 匹配“0”到“9”范围内的所有数字 |
这一部分比较好记,就不再重复出问题了
2.2. 进阶字符
上面的语法已经足够解决绝大部分正则判断了,但需要多种情况的判定则需要更复杂一些的语法字符来辅助判断
代码 | 说明 |
---|---|
| | 分支条件,当筛选的数据有多种规则时,满足其中一种规则即可当成匹配 |
() | 子表达式,也称“分组” |
2.2.1. 分组
分组是可以被命名的,分为三种情况,以\b(\w+)\b\s+(\w+)\b为例子,该范式匹配所有重复单词如 go go 这样的内容
- 自动命名:使用分组时,如果仅使用()作为分组,则会自动分配一个序号,如\b(\w+)\b\s+\1\b,其中\1指(\w+),序号以此类推
- 手动命名:\b(?<Word>\w+)\b\s+\k<Word>\b ,这里我们将第一个分组条件命名为Word
- 不分配不命名:\b(?:\w+)\b\s+(\w+)\b
2.2.2. 断言
有些教程把这一项叫做“零宽断言”,这四个字我是难以认全,我感觉断言更能描述下面四个语法,这四个语法很难描述,直接用Wiki的例子做说明比较好
- 正向肯定断言:(?=pattern),例如“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”
- 正向否定断言:(?!pattern),例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”
- 反向肯定断言:(?<=pattern),例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”
- 反向否定断言:(?<!pattern),例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”
2.2.3. 贪婪与懒惰
默认情况下,正则表达式会优先尽可能多的字符,这称之为贪婪,那相反地,匹配尽可能少的字符则是懒惰(Non-greedy quantifiers),当需要尽量少匹配的时,则可以使用“?”
例如o+对于字符串oooo将匹配所有的o,但o+?则将匹配单个o
3. Python正则表达式 - RE模块
Python中的RE模块提供与Perl语言类似的正则表达式匹配
Python字符串中使用反斜杠\来转义特殊符号,以免引发其特殊含义,例如包含单引号的Python字符串'Chancel\'s blog',正则表达式也采用了正斜杠来表达转义,那么如果需要在Python中的正则表达式字符串植入单引号,则面临需要写成'Chancel\\'s blog'的尴尬情况,为了使字符串中的反斜杠不被转义,Python允许使用在字符串前面添加r来表示字符串中的反斜杠不被转义,如r''Chancel\'s blog'
正则表达式的详细文档可以参考 官方文档 | re -- 正则表达式
以下说明基于Python版本3.9.2挑选几个重要的方法作为例子
3.1. compile
方法 re.compile(pattern, flags=0)可以编译正则表达式,用于匹配,在多次使用正则表达式的场景下可以提前编译正则表达式来提高匹配效率
例如匹配问号前的单词
import re
content = 'Can you turn the declarative sentence into a rhetorical question?'
re_compile = re.compile(r'\b\w+\?', re.I)
print(re_compile)
# output: re.compile('\\b\\w+\\?', re.IGNORECASE)
Flag参数
标识 | 含义 |
---|---|
re.A | ASCII,让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII |
re.I | IGNORECASE,忽略大小写 |
re.L | LOCALE,由当前语言区域决定 \w, \W, \b, \B 和大小写敏感匹配 |
re.M | MULTILINE,让'^'和'$'匹配每一行 |
re.S | DOTALL,让'.'匹配所有字符,包含换行符! |
re.X | VERBOSE,允许添加注释、空白符 |
3.2. search
方法re.search是非常常用的一个方法,用于匹配第一个相应的匹配对象
import re
result = re.search(r'o','oooo')
print(result.group(0))
# output: o
3.3. split
split可以将字符串按正则表达式全部分组
import re
>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']
Python的正则表达式还有非常多的知识,如果正则表达式基础过关,也可以直接参考官方例子