python正则表达式

2 阅读7分钟

正则表达式

一、什么是正则表达式?

正则表达式,英文叫:

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']