正则表达式
一、什么是正则表达式?
正则表达式,英文叫:
Regular Expression
通常简称:
Regex
它本质上是一套描述字符串规律的规则。
比如现在有一段文本:
text = """
My phone number is 13812345678.
My friend's number is 15987654321.
This one is invalid: 12345.
"""
如果让我们找所有手机号,我们当然可以自己写很多 if 判断。
但我们真正想表达的是:
我要找一个:
第一位是
1第二位是
3~9后面还有 9 个数字
总共 11 位
用正则表达式就可以写成:
r'1[3-9]\d{9}'
所以你可以把正则理解成:
用一种特殊的语言描述“我要找什么样的字符串”。
二、最重要的思想:正则就是“一个一个位置去描述”
比如:
r'abc'
非常简单,它的意思就是:
第一个位置:a
第二个位置:b
第三个位置:c
所以:
import re
text = "hello abc world"
result = re.findall(r'abc', text)
print(result)
结果:
['abc']
这里根本没有什么复杂的东西。
三、普通字符
正则里,大部分普通字符就是匹配它自己。
例如:
r'cat'
就是寻找:
cat
例如:
import re
text = "I have a cat and a dog."
print(re.findall(r'cat', text))
结果:
['cat']
同理:
r'hello'
就是找:
hello
四、真正让正则强大的:特殊符号
正则里面有一些字符不是表示自己,而是有特殊含义。
例如:
\d
.
[]
*
+
?
{}
^
$
()
|
\b
这些一般称为:
元字符(Metacharacters)
接下来一个一个理解。
五、\d:匹配一个数字
这是你已经用过的。
r'\d'
意思是:
匹配一个数字字符。
例如:
text = "abc123"
print(re.findall(r'\d', text))
结果:
['1', '2', '3']
注意:
\d
一次只匹配 一个数字。
所以:
abc123
↑↑↑
实际上是分别匹配:
1
2
3
你可以暂时把:
\d
理解成:
[0-9]
在 Python 默认的 Unicode 模式下,\d 的范围实际上还可能包含某些非 ASCII 数字字符,不过初学时先把它理解成“数字”就行。
六、\D:匹配非数字
注意大小写。
\d
是:
数字
而:
\D
是:
不是数字
例如:
text = "abc123"
print(re.findall(r'\D', text))
结果类似:
['a', 'b', 'c']
所以很多正则都有这种规律:
小写:某种东西
大写:不是这种东西
七、\w:匹配“单词字符”
\w
通常可以理解为匹配:
字母
数字
下划线 _
例如:
text = "abc_123!"
print(re.findall(r'\w', text))
会匹配:
a
b
c
_
1
2
3
但:
!
不会。
所以:
\w
可以简单记成:
word character
八、\W:非单词字符
和前面一样:
\w
表示单词字符。
\W
表示:
非单词字符。
例如:
text = "abc 123!"
print(re.findall(r'\W', text))
会匹配:
空格
!
九、\s:空白字符
\s
表示空白字符,例如:
空格
Tab
换行
例如:
text = "hello world"
print(re.findall(r'\s', text))
会找到中间的空格。
可以记:
s → space
虽然严格来说它不仅仅代表 space。
十、.:任意字符
. 非常重要。
在正则里面:
.
通常表示:
任意一个字符。
例如:
r'a.c'
意思是:
a
+
任意一个字符
+
c
所以:
abc
adc
a1c
a-c
a c
都可以匹配。
例如:
text = "abc adc a1c"
print(re.findall(r'a.c', text))
得到:
['abc', 'adc', 'a1c']
所以一定注意:
.
在正则中一般不是“句号”。
它是:
任意字符。
十一、[]:指定“这个位置允许出现什么”
这是你手机号里面:
[3-9]
的知识。
例如:
[abc]
意思不是:
abc
而是:
这个位置可以是
a、b、c中任意一个。
所以:
r'[abc]'
可以匹配:
a
或者:
b
或者:
c
比如:
r'[0-9]'
就是:
0 到 9 中任意一个数字。
类似:
r'[a-z]'
表示:
任意一个小写英文字母。
r'[A-Z]'
表示:
任意一个大写英文字母。
所以你之前写:
r'1[3-9]\d{9}'
其中:
[3-9]
就是:
这个位置允许是 3、4、5、6、7、8、9。
十二、现在重新拆你的手机号正则
你的:
r'1[3-9]\d{9}'
我们现在可以逐字符看。
首先:
1
第一位必须是:
1
然后:
[3-9]
第二位必须是:
3~9
然后:
\d
表示一个数字。
但是你不是写:
\d
而是:
\d{9}
所以接下来就要讲 {}。
十三、{n}:前面的东西重复 n 次
例如:
\d{3}
意思是:
一个数字连续出现 3 次。
也就是:
数字 数字 数字
例如:
123
456
999
001
都符合:
\d{3}
所以:
\d{9}
就是:
连续 9 个数字。
现在你的手机号:
1[3-9]\d{9}
就可以完整翻译了:
1
↓
第一位必须是 1
[3-9]
↓
第二位必须是 3 到 9
\d{9}
↓
后面必须再有 9 个数字
也就是:
1位 + 1位 + 9位
= 11位
十四、{n,m}:重复 n 到 m 次
除了:
{9}
还可以写:
{2,5}
意思是:
前面的东西重复至少 2 次,最多 5 次。
例如:
r'\d{2,5}'
可以匹配:
12
123
1234
12345
还有:
{2,}
意思是:
至少出现 2 次,上不封顶。
十五、+:前面的东西出现 1 次或更多次
例如:
\d+
意思是:
连续一个或多个数字。
例如:
text = "abc 123 xyz 45678"
print(re.findall(r'\d+', text))
结果:
['123', '45678']
这里特别重要。
如果写:
\d
结果会是:
['1', '2', '3', '4', '5', '6', '7', '8']
而:
\d+
会把连续数字整体匹配:
['123', '45678']
可以记:
+ = 一个或多个
十六、*:0 次或更多次
*
意思是:
前面的东西可以不出现,也可以出现很多次。
例如:
ab*c
中间的:
b*
表示:
b 可以出现 0 次、1 次、2 次、3 次……
所以:
ac
abc
abbc
abbbc
都可以。
记:
* = 0 个或多个
十七、?:0 次或 1 次
?
意思是:
前面的东西可有可无。
例如:
colou?r
这里:
u?
表示 u 可以出现一次,也可以不出现。
因此:
color
可以匹配。
colour
也可以匹配。
记:
? = 0 或 1
到这里,三个东西非常容易混:
这个表值得直接记下来。
十八、^:字符串开头
现在回到刚才让你懵的地方。
^
表示:
字符串的开头位置。
注意!
^ 不是匹配一个字符。
它是在检查一个位置。
例如:
r'^hello'
意思是:
hello必须出现在字符串开头。
所以:
hello world
✅ 匹配。
而:
say hello
❌ 不匹配。
因为 hello 没有在开头。
你可以想象:
^ hello world
↑
字符串开头
十九、$:字符串结尾
和 ^ 相反:
$
表示:
字符串结尾。
例如:
r'world$'
要求:
world必须出现在字符串结尾。
所以:
hello world
✅
但:
world hello
❌
二十、^...$ 一起使用
这个特别重要。
假设我们写:
r'^\d{3}$'
它的意思是:
^
从字符串开头开始
\d{3}
正好三个数字
$
然后马上到字符串结尾
所以:
123
✅
但是:
abc123
❌
123abc
❌
1234
❌
因此:
r'^1[3-9]\d{9}$'
表示:
整个字符串必须完整地是一个手机号。
例如:
"13812345678"
✅
而:
"我的手机号13812345678"
❌
这是 ^ 和 $ 最大的作用。
二十一、\b 到底是什么?
这个确实比 ^ $ 抽象,所以你刚才懵是很正常的。
\b 表示:
单词边界(word boundary)
它也不是一个真正的字符。
它表示一个位置。
例如:
I like python very much
↑ ↑
python 两边存在边界。
所以:
r'\bpython\b'
可以理解成:
我要独立出现的
python。
例如:
python
✅
I like python.
✅
但是:
python123
❌
mypython
❌
因为这时候 python 和旁边的“单词字符”黏在一起了。
二十二、暂时怎么理解 \b 最简单?
你现在可以不要研究它的内部原理。
先简单记:
\bcat\b
大概相当于:
找独立出现的
cat。
例如:
I have a cat.
✅
但是:
category
❌
因为:
category
^^^
cat
这里虽然有 cat 这三个字符,但是它只是另一个单词的一部分。
这就是为什么我们使用:
\bcat\b
二十三、|:或者
这个也很常用。
例如:
cat|dog
意思是:
cat 或者 dog
例如:
text = "I have a cat and a dog"
print(re.findall(r'cat|dog', text))
结果:
['cat', 'dog']
可以记:
| = OR
跟编程里的:
or
很像。
二十四、():分组
括号的作用之一是:
把一些东西看成一个整体。
例如:
(ab)+
这里不是:
b 重复
而是:
ab
整个重复。
因此可以匹配:
ab
abab
ababab
因为:
(ab)(ab)(ab)
例如电话号码:
010-12345678
021-87654321
可以写:
r'\d{3}-\d{8}'
如果某一部分更复杂,就可能需要括号组合。
二十五、\:转义
这个非常重要。
我们刚才说:
.
表示:
任意字符
那如果我真的就想匹配一个句号:
.
怎么办?
使用:
\.
也就是:
r'\.'
例如:
text = "hello.com"
print(re.findall(r'\.', text))
结果:
['.']
所以:
. 任意字符
\. 真正的句号
类似:
\d
\s
\w
\b
中的 \ 都有特殊作用。
二十六、为什么 Python 正则前面经常写 r?
你会发现我们一直写:
r'\d+'
而不是:
'\d+'
这里:
r
叫:
raw string,原始字符串。
原因是 Python 本身也使用 \ 作为转义符。
例如:
"\n"
表示:
换行。
但正则表达式自己又大量使用:
\d
\b
\s
\w
所以 Python 和正则可能产生冲突。
因此写正则时通常养成习惯:
r'正则表达式'
比如:
r'\d+'
而不是:
'\d+'
你现在可以简单记住:
写 Python 正则,前面基本都加
r。
二十七、正则表达式和 re 是什么关系?
正则表达式是:
r'\d+'
这套规则。
而:
re
是 Python 提供的:
正则表达式模块。
所以首先:
import re
然后:
re.findall(...)
这些是 Python 给我们的操作正则的方法。
二十八、re.findall() 是干什么的?
你目前最熟悉的应该是:
re.findall(pattern, text)
意思是:
把文本中所有符合正则规则的内容全部找出来。
例如:
import re
text = "I have 10 apples and 20 bananas."
result = re.findall(r'\d+', text)
print(result)
结果:
['10', '20']
为什么?
因为:
\d+
表示:
一个或多个连续数字。
二十九、还有几个常用的 re 方法
现在不用全部背,但是先建立概念。
re.findall()
寻找所有匹配。
re.findall(r'\d+', text)
返回:
['10', '20']
re.search()
寻找第一个匹配。
re.search(r'\d+', text)
找到第一个以后就停。
re.match()
尝试从字符串开头进行匹配。
例如:
re.match(r'\d+', "123abc")
可以匹配。
但是:
re.match(r'\d+', "abc123")
不行。
re.fullmatch()
要求:
整个字符串全部符合规则。
这对手机号验证特别方便。
例如:
phone = "13812345678"
result = re.fullmatch(r'1[3-9]\d{9}', phone)
print(result)
如果整个字符串符合手机号规则,就成功。
所以以后你会发现:
re.fullmatch(r'1[3-9]\d{9}', phone)
和我们写:
re.match(r'^1[3-9]\d{9}$', phone)
在这种简单场景下目标很接近。
对于“验证整个字符串”,fullmatch() 往往更直观。
三十、现在建立一张正则表达式地图
你现在学正则,不需要乱背。
可以把知识分成 5 大类。
第一类:我要匹配什么字符?
\d 数字
\w 单词字符
\s 空白字符
. 任意字符
[abc] a/b/c 中任意一个
[0-9] 0~9 中任意一个
[a-z] a~z 中任意一个
第二类:我要几个?
* 0个或多个
+ 1个或多个
? 0个或1个
{3} 正好3个
{2,5} 2~5个
第三类:我要它出现在哪里?
^ 字符串开头
$ 字符串结尾
\b 单词边界
第四类:我要怎么组合?
() 分组
| 或者
第五类:Python 怎么使用它?
re.findall()
re.search()
re.match()
re.fullmatch()
re.sub()
re.split()
这样一分,正则就没有看起来那么乱了。
三十一、重新来看你的手机号
我们现在应该已经可以很自然地读:
r'1[3-9]\d{9}'
不要把它看成:
一串神秘符号
而是一段语言:
1
↓
第一个字符必须是 1
[3-9]
↓
第二个字符必须是 3~9 中的一个
\d
↓
数字
{9}
↓
前面的数字重复 9 次
最终:
1 + [3-9] + \d{9}
第一位 第二位 后9位
1 3~9 数字
例如:
13812345678
││└─────────┘
││ 9位数字
│└─ 3~9
└── 1
所以它匹配。
而:
23812345678
不匹配,因为:
第一位是2
而正则要求:
第一位必须是1
三十二、现在你最应该记住的版本
如果你现在要整理笔记,我建议这一部分直接记下来:
正则表达式 Regex:
用于按照某种“规则”匹配、查找和处理字符串。
常用规则:
\d 一个数字
\D 一个非数字
\w 一个单词字符
\W 一个非单词字符
\s 一个空白字符
\S 一个非空白字符
. 任意一个字符
[abc] a、b、c中的一个
[a-z] a到z中的一个
[0-9] 0到9中的一个
* 前面的内容出现0次或更多
+ 前面的内容出现1次或更多
? 前面的内容出现0次或1次
{n} 前面的内容正好出现n次
{n,m} 出现n到m次
^ 字符串开头
$ 字符串结尾
\b 单词边界
| 或
() 分组
Python 中:
import re
最常见:
re.findall(pattern, text)
表示:
找出
text中所有符合pattern的内容。
例如:
re.findall(r'\d+', 'abc123 def456')
得到:
['123', '456']