正则表达式,又称规则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。

baidu百科

简单来说,正则表达式就是对字符串进行操作的一种逻辑公式,用事先定义好的⼀些规则,组成⼀个规则字符串,然后用这个规则字符串去匹配符合这个规则的字符串,实现对字符串的匹配和过滤。本文基于python中的re模块来介绍正则表达式的常见用法,但这并非Python独有的,绝大多数语言都支持正则表达式。

常用方法

模式 描述
^ 匹配字符串的开头,^\d表示必须以数字开头
$ 匹配字符串的末尾,\d$表示必须以数字结束
. 匹配除\n外的任意单字符。要匹配包括 \n 在内的任意单字符,可使用[.\n]的模式。当re.DOTALL标记被指定时,则也可匹配包括换行符的任意字符。
[…] 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,’m’或’k’
[^…] 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。
* 匹配0个或多个的表达式。大于等于0个。
+ 匹配1个或多个的表达式。至少1个。
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n} 精确匹配n个前面的表达式。例如,o{2}不能匹配”Bob”中的o,但是能匹配”food”中的两个o。
{n,} 至少匹配n个前面表达式。o{1,}等价于o+o{0,}则等价于o*
{n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
( ) 匹配括号内的表达式,也表示一个组
a|b 匹配a或b
\w 匹配数字字母下划线。等价于[A-Za-z0-9_]
\W 匹配非数字字母下划线。等价于[^A-Za-z0-9_]
\s 匹配任意空白字符,包括空格、制表符、换页符等。等价于[\t\n\r\f\v]
\S 匹配任意非空字符。等价于 [^\t\n\r\f\v]
\d 匹配任意数字,等价于[0-9]
\D 匹配任意非数字,等价于[^0-9]
\A 匹配字符串开始
\Z 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串
\z 匹配字符串结束
\G 匹配最后匹配完成的位置
\n 匹配一个换行符
\t 匹配一个制表符

实例

Python中的re模块

python的re模块提供了类似于Perl的正则表达式匹配操作。 被搜索匹配的字符串既可以是 Unicode 字符串 (str),也可以是8位字节串 (bytes)。但是两者不能混用。即不能用一个字节串(bytes)模式去匹配 Unicode 字符串 (str),反之亦然。

注意:正则表达式使用反斜杠('\')来表示特殊形式,或者把特殊字符转义成普通字符。 而反斜杠在普通的 Python 字符串里也有相同的作用, 所以就产生了冲突。 解决办法是对于正则表达式样式使用 Python 的原始字符串表示法;在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。因此 r"\n" 表示包含 '\''n' 两个字符的字符串,而 "\n" 则表示只包含一个换行符的字符串。

s = 'ABC\\-001'
# 'ABC\-001'
# 使用Python的r前缀,就不用考虑转义的问题了 r'raw'
s = r'ABC\-001'
# 'ABC\-001'

前面的一个 r 表示字符串为非转义的原始字符串,让编译器忽略反斜杠,也就是忽略转义字符。原始字符串记法 (r”text”) 保持正则表达式正常。否则,每个正则式里的反斜杠(’\‘) 都必须前缀一个反斜杠来转义。

print(re.match("\W(.)\1\W", " ff "))
print(re.match(r"\W(.)\1\W", " ff "))
print(re.match("\\W(.)\\1\\W", " ff "))

# output 
None
<_sre.SRE_Match object; span=(0, 4), match=' ff '>
<_sre.SRE_Match object; span=(0, 4), match=' ff '>

下面介绍一下python标准库re模块的几种常用方法。

1. re.match()

re.match(pattern, string, flags=0)

match()尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。如果匹配成功,返回一个Match对象。常见的判断方法是:

import re
content = 'Hello~666 | fr0m_Qi_Zhang|:)'
print(len(content))
result = re.match(r'^Hello~\d{3}\s\|\s\w+\|:\)$', content)
# 也可以用.*进行泛匹配:
# result = re.match(r'^Hello.*:\)$', content)
print(result)
print(result.group())
print(result.span())

# output
30
<_sre.SRE_Match object; span=(0, 30), match='Hello~666 | fr0m_Qi_Zhang|:)'>
Hello~666 | fr0m_Qi_Zhang|:)
(0, 30)

1.1 分组

()匹配目标,然后用group方法提取()中的内容,可用这种方法提取字符串中的子串。 如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。 group(0)是原始字符串,group(1)group(2)…… 表示第1、2、…… 个子串。 例如:

import re
m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
m
# <_sre.SRE_Match object; span=(0, 9), match='010-12345'>
m.group(0)
# '010-12345'
m.group(1)
# '010'
m.group(2)
# '12345'

^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码。

>>> t = '19:05:30'
>>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
>>> m.groups()
('19', '05', '30')

上面这个正则表达式可以直接识别合法的时间。

import re
content = 'Hello~666 | fr0m_Qi_Zhang | :)'
print(len(content))
result = re.match(r'^Hello(~\d{3}).*:\)$', content)
print(result)
print(result.group(0))
print(result.group(1)) # 提取(~\d{3})匹配的内容:~666
print(result.span())

# output
32
<_sre.SRE_Match object; span=(0, 32), match='Hello~666 | fr0m_Qi_Zhang | :)'>
Hello~666 | fr0m_Qi_Zhang | :)
~666
(0, 32)

分组例子:

import re
pattern = re.compile(r'([a-z]+) ([a-z]+)', re.I)   # re.I 表示忽略大小写
m = pattern.match('Hello World Wide Web')
print(m.group())
print(m.groups())
print(m.group(0))
print(m.span(0))
print(m.group(1))
print(m.span(1))
print(m.group(2))
print(m.span(2))
print(m.group(3))
"""
Hello World
('Hello', 'World')
Hello World
(0, 11)
Hello
(0, 5)
World
(6, 11)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-9-797c6bfab8b0> in <module>
      7 print(m.group(2))
      8 print(m.span(2))
----> 9 print(m.group(3))
     10 

IndexError: no such group
"""
pattern = re.compile(r'([a-z]+) (?:[a-z]+)', re.I)   # re.I 表示忽略大小写
m = pattern.match('Hello World Wide Web')
print(m.groups())
# ('Hello',)

1.2 贪婪匹配与非贪婪匹配

正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。例如:

re.match(r'^(\d+)(0*)$', '1008600000').groups()
# ('1008600000', '')

\d+贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串。 加?可让\d+采用非贪婪匹配(尽可能少匹配):

re.match(r'^(\d+?)(0*)$', '1008600000').groups()
('10086', '00000')

(1)贪婪匹配

import re
content = 'Hello~666 | fr0m_Qi_Zhang | :)'
result = re.match(r'^He.*(\d+).*:\)$', content) 
print(f"result: {result}")
print(f"result.group(1): {result.group(1)}")

# output
result: <_sre.SRE_Match object; span=(0, 32), match='Hello~666 | fr0m_Qi_Zhang | :)'>
result.group(1): 0
# .*会进行贪婪匹配。使得(\d+)匹配到了fr0m中的0

(2)非贪婪匹配

.*后面加一个?就会变成非贪婪匹配:

import re
content = 'Hello~666 | fr0m_Qi_Zhang | :)'
result = re.match(r'^He.*?(\d+).*:\)$', content) 
print(f"result: {result}")
print(f"result.group(1): {result.group(1)}")

#output
result: <_sre.SRE_Match object; span=(0, 32), match='Hello~666 | fr0m_Qi_Zhang | :)'>
result.group(1): 666
# 括号中的(\d+)还是进行了贪婪匹配,改为(\d+?)会只匹配一个6

1.3 匹配模式

对于字符串中的换行符进行匹配:

import re
content = '''Hello~666 | 
fr0m_Qi_Zhang | :)
'''
result = re.match(r'^He.*?(\d+).*:\)$', content, re.S)
print(result.group(1))

#output
666

re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)

其他常用的匹配模式:

修饰符 描述
re.I 忽略大小写匹配
re.M re.MULTILINE,多行匹配。'^' 匹配字符串的开始,和每一行的开始;'$'匹配字符串尾,和每一行的结尾
re.S re.DOTALL,使 . 匹配包括换行在内的所有字符
re.X re.VERBOSE,这个标记允许你通过分段和添加注释,编写更具可读性更友好的正则表达式

1.4 转义

import re
content = 'The price of the book is $66.66'
result = re.match('The price of the book is $66.66', content)
print(result) 
# None

在匹配的时候对于一些特殊字符需要加\进行转义,不然会匹配错误。上述代码可以改成如下:

import re
content = 'The price of the book is $66.66'
result = re.match('The price of the book is \$66\.66', content)
print(result)
# <_sre.SRE_Match object; span=(0, 31), match='The price of the book is $66.66'>

re.search(pattern, string, flags=0)

re.match()只匹配字符串的开始,如果字符串开始不符合正则表达式,就会匹配失败,函数返回None;与re.match()不同的是,re.search()会扫描整个字符串并返回第一个成功的匹配。

其他方法和re.match()类似。所以为了匹配更方便,能用search就不用match。

import re
content = "wawawa Hello~666 | fr0m_Qi_Zhang | :)"
# match()
result = re.match(r'He.*?(\d+).*:\)', content)
if result:
    print(result.group(1))
else:
    print(result)
# search()
result = re.search(r'He.*?(\d+).*:\)', content)
if result:
    print(result.group(1))
else:
    print(result)
    
# output 
None
666

3. re.findall()

re.findall(pattern, string, flags=0) 在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。匹配按找到的顺序返回。如果样式里存在一到多个组,就返回一个组合列表;就是一个元组的列表。match()search() 是匹配一次 findall() 匹配所有。

text = "He was carefully carefully carefully disguised but captured quickly by police."
re.findall(r"\w+ly", text)
# ['carefully', 'carefully', 'carefully', 'quickly']
import re
pattern = re.compile(r'\d+')   # 查找数字
result1 = pattern.findall('what 123 123 how where 456 also 123')
result2 = pattern.findall('6688why12', pos=0, endpos=3)
print(result1)
print(result2)

# output
['123', '123', '456', '123']
['668']

4. re.finditer()

以迭代器的形式返回匹配对象。从左到右扫描,匹配按顺序排列。

text = "He was carefully carefully carefully disguised but captured quickly by police."
for m in re.finditer(r"\w+ly", text):
    print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))

# output
07-16: carefully
17-26: carefully
27-36: carefully
60-67: quickly

5. re.split()

按照正则表达式,切分字符串

re.split(pattern, string[, maxsplit=0, flags=0])
#参数:
pattern	    匹配的正则表达式,如果有`()`,则所有的组里的文字也会包含在列表里。
string	    要匹配的字符串。
maxsplit	分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
flags	    标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。
import re
print('a b   c'.split(' '))
# ['a', 'b', '', '', 'c']
print(re.split(r'\s+', 'a b   c'))
# ['a', 'b', 'c']
print(re.split(r'[\s,]+', 'a,b, c  d'))
# ['a', 'b', 'c', 'd']
print(re.split(r'[\s,;]+', 'a,b;; c  d'))
# ['a', 'b', 'c', 'd']

官网的例子:

print(re.split(r'\W+', 'Words, words, words.'))
# ['Words', 'words', 'words', '']
print(re.split(r'(\W+)', 'Words, words, words.'))
# ['Words', ', ', 'words', ', ', 'words', '.', '']
print(re.split(r'\W+', 'Words, words, words.', 1))
# ['Words', 'words, words.']
print(re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE))
# ['0', '3', '9']

6. re.sub()

替换字符串中每一个匹配的子串后返回替换后的字符串。

import re
content = "wawawa Hello~666 | fr0m_Qi_Zhang | :)"
content = re.sub('\d+', '@@@Replacement@@@', content)
print(content)

# output
wawawa Hello~@@@Replacement@@@ | fr@@@Replacement@@@m_Qi_Zhang | :)
import re
content = "wawawa Hello~666 | fr0m_Qi_Zhang | :)"
content = re.sub('[_]', ' ', content)
print(content)

#output
wawawa Hello~666 | fr0m Qi Zhang | :)

7. re.compile()

re.compile()将一个正则表达式串编译成正则对象,以便于复用该匹配模式。

在Python中使用正则表达式时,re模块内会有两个步骤:编译正则表达–>匹配字符串

但若一个正则表达式要重复使用很多次,可以预编译该正则表达式以提高效率,编译后生成Regular Expression对象,可以重复调用:

import re
content = "wawawa Hello~666 | fr0m_Qi_Zhang | :)"
pattern = re.compile(r'\d+', re.S)
result = re.search(pattern, content)
print(result)
#output
<_sre.SRE_Match object; span=(13, 16), match='666'>
import re
# 编译:
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
print(re_telephone.match('010-12345').group())
print(re_telephone.match('010-8086').groups())
# output
010-12345
('010', '8086')

Reference