Python 正则表达式教程

在本教程中,我们将学习正则表达式以及 Python re 中模块中定义的正则表达式操作。re 是 Python 的标准库,它支持正则表达式的匹配操作。

Python 中的正则表达式是一组字符或序列,用于使用正式语法将字符串与另一个模式匹配。你可以将正则表达式视为嵌入在 python 中的小型编程语言。

你可以使用正则表达式来定义一些规则,然后使用这些规则从你希望与模式匹配的给定字符串中创建可能的字符串。Python 中的正则表达式被解释为一组指令。

match() 函数:

你可以使用 match() 函数将 RE 模式与给定字符串来匹配。match() 函数也包含了标志,标志定义正则表达式的行为,它可以有不同的值,后续我们在本教程会继续讲到。

以下是 Python 中 match() 函数的语法:

re.match(pattern, string, flags)

它有三个参数,

  1. pattern 是要匹配的正则表达式模式
  2. string 是与正则表达式匹配的给定字符串
  3. flags 用于更改正则表达式的行为,这是个可选项

如果匹配成功就返回 Match 对象,否则返回 NONE。匹配对象 Match 还有两个主要方法,即 group(num)group()。可以使用这些方法来分别返回匹配的特定子序列和所有子序列。

使用 match 函数

我们来看下如何使用 match 函数,

import re
strTest = "Hello Python Programming"
mobj = re.match(r"hello", strTest, re.I)
print(mobj.group())

第一行如何导入了 re 模块,

第二行是我们需要匹配的字符串 Hello Python Programming

第三行是需要将该字符串跟匹配模式 r"Hello"比较,并且 re.I 指定的匹配模式是忽略大小写。最终匹配的结果赋值给 mobj

第四行将匹配的结果给打印出来,结果如下,

Hello

在此示例中,使用前缀 r 来表示字符串是原始字符串。在原始字符串中,在使用转义序列时不需要写双斜杠,例如,如果你想要一个反斜杠,那么你只需要一个\,而不用像普通字符串那样的双反斜杠\\

match 函数与常规字符串

我们来看一个不用原始字符串而用常规字符串来做匹配的例子,

import re
str = "\\tHello Python Programming"
mobj = re.match("\\thello", str, re.I) #no match

str = "\tHello Python Programming"
mobj = re.match("\\thello", str, re.I) #\thello is matching

search() 函数

你可以使用 search() 函数搜索给定字符串中的正则表达式匹配模式。search 有三个输入参数,匹配模式,给定字符串以及可选的匹配行为选项 flags

以下是 Python 中 search 函数的语法,

re.search(pattern, string, flags)

我们来看具体的 search() 函数使用实例,

import re
str = "Hello Python Programming"
sobj = re.search(r"programming", str, re.I)
print(sobj.group())
Programming

在此代码中,我们来搜索给定字符串中是否存在 programmingsearch 函数搜索整个字符串。搜索 search 和匹配 match 之间的区别在于 match 函数只检查字符串的开头,而 search 在整个字符串中搜索。

在字符串开头搜索

如果你想在字符串的开头搜索,那么可以使用^。来看下例,

import re
str = "Hello Python Programming"
sobj = re.search(r"^programming", str, re.I)
print(sobj.group()) #no match is found

sobj = re.search(r"^hello", str, re.I)
print(sobj.group()) #matching: Hello

这里, ^ 的意思是只在字符串的开头进行搜索,假如开头不匹配的话,就返回 None,而不管字符串后续中有没有再匹配到。

在字符串结尾搜索

你也可以在给定字符串的末尾搜索,通过在模式后面加$来限定。来看这个例子,

import re
str = "Hello Python Programming"
sobj = re.search(r"programming$", str, re.I)
print(sobj.group()) #matching: Programming

sobj = re.search(r"hello$", str, re.I)
print(sobj.group()) #no match found

编译正则表达式

Python 中的正则表达式在编译时将转换为模式,这些模式实际上是包含不同功能的模式对象,以执行不同的任务,包括搜索,匹配和替换等。

编译模式后,你可以稍后在程序中使用该模式。

使用预编译的模式

在下面的例子中,被编译的模式 r"\d" 意思是在字符串中的第一个数字。当该模式后续跟 search 一起使用的时候,它搜索输入字符串中的一个数字;同样的,你也可以用这个模式跟 match 搭配来找到给定字符串中的匹配。

import re
compPat = re.compile(r"(\d)")
sobj = compPat.search("Lalalala 123")
print(mobj.group())

mobj = compPat.match("234Lalalala 123456789")
print(mobj.group())
1
2

标志位 Flags

你可以使用标志位 Flags 来改变正则表达式的行为。在正则函数中,标志位是可选项。你可以通过两种不同的方式来使用标志,即使用关键字 flags 并为其分配标志值或直接写入标志位的值。你可以设置多个标志位,这可以通过使用按位或运算|符来完成。

下表列出了正则表达式的一些常用标志,

标志位 说明
re.I 在匹配时忽略字符串和模式的大小写
re.L 匹配{\w \W \b \B}跟本地语言相关。不推荐使用
re.M $匹配行末尾,而不是字符串末尾,同理^匹配行开头而不是字符串开头
re.S .匹配任何字符,也包括新的一行
re.U 使用 Unicode 字符集
re.X 忽略各种空格以及以#开头的注释,这使得长匹配模式可以分行来写,提高了可读性

使用多个标志位

我们用下面的代码来演示如何来使用多个标志位来修改正则表达式的行为结果,它们是通过或逻辑符|分开的。

import re
s = re.search("L", "Hello")
print(s)		#Output: None, L is there but in small letter and we didn't use flags

s = re.search("L", "Hello", re.I)
print(s)		#Output: 1

s = re.search("L", "^Hello", re.I | re.M)
print(s)		#Output: 1, searching will be made from the start of line and case is ignored

检查允许的字符

你可以检查一个特定的字符串中是否是否含有特性的字符集。

定义函数并检查允许的字符

在下面的例子中,我们定义了一个函数,并使用预编译模式来检查某些字符是否在给定的字符串中,

import re
def check(str):
	s = re.compile(r'[^A-Z]')
	str = s.search(str)
	return not bool(str)
print(check("HELLOPYTHON"))		# 输出: True
print(check("hellopython"))		# 输出: False

在此函数中,模式 r '[^A-Z]'被编译并使用它来搜索在调用名为 check 的函数中传递的字符串。此函数实际检查传递的字符串是否包含大写字母。类似地,你可以看到当你传递的字符串中只含有小写字母时返回 false

In this function, a pattern that is r '[ ^A-Z]' is compiled and used it to search in a string passed when this function named check is called. This function actually checks if the passed string contains letters A-Z (uppercase) or not. Similarly, it can be seen that when you pass a string in lowercase letters false is returned, this is because re.I flag value is not used which ignores case.

搜索和替换

re 模块提供了一个 sub 函数来替换给定字符串 string 中出现的所有符合 pattern 匹配模式的情况,用来替换的字符串是 repl。你可以通过 count 关键字参数指定最大的替换次数,假如 count 没有给定的话,那就没有最大替换次数限制。sub 函数返回了一个新的字符串。

以下是 sub 函数的语法,

re.sub(pattern, repl, string, count = 0)

sub 函数举例

下面的例子中,sub 函数将整个字符串用新的字符串替换掉了,

import re
s = "Playing 4 hours a day"
obj = re.sub(r'^.*$',"Working",s)
print(obj)
Working

这里,模式 r'^.*$ 中,^$意思是从开头到结尾,.*意思是匹配字符串中的任意字符,它们结合起来就是匹配从开头到结尾的任意字符。 "Working"将来替换整个字符串 s.

使用 sub 函数来删除字符串中的所有数字

下面的例子中,使用 sub 函数来删除给定字符串中的数字,你需要用\d 来匹配数字。

import re
s = "768 Working 2343 789 five 234 656 hours 324 4646 a 345 day"
obj = re.sub(r'\d',"",s)
print(obj)
Working   five   hours   a  day

类似的,你可以来删除字符串中的字母,用 \D 来匹配所有的字母.

import re
s = "768 Working 2343 789 five 234 656 hours 324 4646 a 345 day"
obj = re.sub(r'\D',"",s)
print(obj)
76823437892346563244646345

findall() 函数

The findall 函数返回与模式匹配的所有字符串组成的列表。searchfindall 函数之间的区别在于 findall 查找所有匹配项,而 search 只查找第一个匹配项。findall 函数查找出非重叠的匹配并将其组成列表来返回。

以下是 findall 函数的语法,

findall(pattern, string, flags)

这里, pattern 是正则表达, string 是给定字符串, flags 是前面已经介绍的标志位。

查找所有的非重叠的匹配

下面的例子中,findall 找出所有非重叠的匹配,

import re
str = "Working 6 hours a day. Studying 4 hours a day."
mobj = re.findall(r'[0-9]', str)
print(mobj)
['6', '4']

r'[0-9]' 的意思是匹配所有的数字,最终结果 6, 4 以列表的形式被返回赋值给 mobj

findall 查找文件的内容:

你还可以使用 findall 在文件中查找。当你使用 findall 来查找文件内容时,它将返回文件中所有匹配的字符串的列表,我们可以使用文件的 read() 方法来读取文件中的全部内容,因此你不必来使用循环。下面我们具体通过例子来解释,

import re
file = open('asd.txt', 'r')
mobj = re.findall(r'arg.', file.read())
print(mobj)
file.close()
['arg,', 'arg,', 'arg,', 'argv', 'argv', 'argv']

本例中,文件通过只读模式打开,然后匹配模式 r'arg.' 用来匹配四个字符中其中前三个字符是 arg 而第四个字符任意的情况。所有的匹配字符串通过列表来返回结果。

finditer() 函数

finditer 函数可用于在字符串中查找正则表达式模式并将匹配的字符串以及字符串的位置返回。

以下是 finditer 函数的语法:

finditer(pattern, string, flags)

遍历所有的匹配

findallfinditer 之间唯一的区别是 finditer 返回索引以及匹配的字符串。在下面的代码中,finditer 用于查找匹配字符串的位置,而后通过 for 循环来在遍历所有的匹配(匹配字符串)。

import re
str = "Working 6 hours a day. Studying 4 hours a day."
pat = r'[0-9]'
for mobj in re.finditer(pat, str):
    s = mobj.start()
    e = mobj.end()
    g = mobj.group()
    print('{} found at location [{},{}]'.format(g, s, e))
6 found at location [8,9]
4 found at location [32,33]

split() 函数:

split 函数用于拆分字符串,以下是 split 函数的语法,

split(patter, string, maxsplit=0, flags=0)

这里 maxsplit 是最多的拆分次数,假如能够拆分的次数大于 maxsplit 那么剩余的字符串将作为列表中的最后一个元素。maxsplit 的默认值是 0,意思是可以无限拆分。

拆分一个字符串

我们可以通过 split 来拆分字符串,下面的例子中,字符串根据给定的模式以及最大拆分的数量来拆分。

import re
str = "Birds fly high in the sky for ever"
mobj = re.split('\s+', str, 5)
print(mobj)
['Birds', 'fly', 'high', 'in', 'the', 'sky for ever']

本例中,模式 \s 用来匹配所有的空白字符,它等效于各种空白字符的集合,包括空格,制表符,回车等,具体如[ \t\n\r\f\v]。所以你可以通过它将各个拆分开来。这里的最大拆分次数是 5,所以结果列表中有 6 个元素,最后一个元素是最后一次拆分后剩下的所有的字符串。

Basic patterns of re 的基本模式:

正则表达式可以指定与给定字符串进行比较的模式。以下是正则表达式的基本模式,

模式 描述
^ 在字符串开头匹配
$ 在字符串的结尾处匹配
. 匹配任意一个字符(不包括换行符)
[...] 匹配括号内的单个字符。
[^...] 匹配不在括号中的单个字符
* 给定字符串中出现 0 次或更多次
+ 给定字符串中出现 1 次或多次前面的
? 给定字符串中出现 0 次或 1 次
{n} 匹配给定字符串中出现次数为 n
{n,} 匹配给定字符串中出现次数为 n 次或多次
{n,m} 匹配给定字符串中的出现次数为至少 n 个,最多 m 个
`a b`
(re) 此模式用于对正则表达式进行分组,它将记住匹配的文本,也叫反身引入
(?imx) 它将暂时在 RE 上切换 i 或 m 或 x。使用括号时,只会影响括号区域。
(?-imx) 它会暂时关闭 RE 中的 i 或 m 或 x。使用括号时,只会影响括号区域。
(?: re) 此模式用于对正则表达式进行分组,但不会记住匹配的文本。
(?imx: re) 它将临时在括号内的 RE 或 i 或 RE 中切换。
(?-imx: re) 它会在括号内暂时切换 i 或 m 或 x 中的 RE。
(?#...) 这是一个评论。
(?= re) 它用于通过使用模式指定位置。它没有任何范围。
(?! re) 它用于通过使用模式否定来指定位置。它没有任何范围。
(?> re) 此模式用于匹配独立模式。
\w 此模式用于匹配单词。
\W 此模式用于匹配非单词。
\s 它将匹配空格。\ s 等于[\ t \ n \ r \ n]。
\S 它将匹配非空格。
\d \ d 等于[0-9]。它匹配字符串中的数字。
\D 匹配非数字
\A 匹配字符串的开头。
\Z 匹配字符串的结尾。如果有任何换行符,它将在换行符之前匹配。
\z 匹配字符串的结尾。
\G \ G 用于匹配最后一场比赛结束的点。
\b 匹配在开头或者结尾的空字符
\B 匹配不在开头或者结尾的空字符
\n, \t, etc. \n 用于匹配换行符,\t 将匹配分隔符
\1...\9 匹配第 n 个子表达式(已分组)。
\10 \ 10 通常匹配第 n 个子表达式(已分组),如果匹配已经完成。如果匹配尚未完成\ 10 将提供字符代码的八进制表示。

重复情形

下面的列表中列出了匹配当中的重复情形以及它们的具体说明。

举例 说明
ab? 匹配 a 或者 ab
ab* 匹配 a 或者 a 后面接任意个 b,比如 ab, abb
ab+ 匹配 a 以及至少一个 b
\d{2} 匹配两个数字
\d{2,} 匹配两个或多个数字
\d{2,4} 匹配 2 到 4 个数字

非贪婪匹配

在正则表达式在,重复一般意义上都是贪婪的,也就是它试图尽量多的去匹配最大的重复。限定符 *, +? 是贪婪限定符,当你使用*时,它会进行贪婪匹配,会匹配字符串中尽量多的字符。比如下面的例子,

import re
mobj = re.match(r'.*', "Birds fly high in sky")
print(mobj.group())
Birds fly high in the sky

你能看出来,整个字符串都被匹配了。

当你一起使用 ?.+ 时,就得到了一个非贪婪匹配模式 .+? ,它会尽量少的去匹配字符串中的内容。

import re
mobj = re.match(r'.*', "Birds fly high in sky")
print(mobj.group())

结果就只有一个字符,

B

正则表达式中的特殊字符和转义

re 模块中的特殊字符以 \为开始。比如, \A 匹配字符串的开头。在上面的列表当中,我们已经具体介绍了这些特殊字符,我们将用例子来解释它们的具体用法。

import re
str = "Birds fly high in the sky"
# \A
mobj = re.match(r'\Ab', str, re.I) #OUTPUT: B, here \A will match at beginning only.

#\d
mobj = re.match(r'\d', "4 birds are flying") #OUTPUT: 4

#\s
mobj = re.split('\s+', "birds fly high in the sky", 1) #OUTPUT: ['Birds', 'fly']

转义函数 escape

escape 函数用来转义字符串中的字符,ASCII 字母、数字以及下划线_不会被转义。下面是 escape 函数的语法,

escape(pattern)

在下面的例子中,字符串 www.python.com 被传入到 escape 函数,其中的.是一个特殊的元字符,将会被转义成\.

print(re.escape('www.python.com'))
www\.python\.com

一般而言,特殊的元字符会通过在前面加反斜杠\来转义。

转义特殊字符

比如说像括号 []这样的字符,在正则表达式中具有特殊的意义。我们先看一个例子,

import re
mobj = re.search(r'[a]', '[a]b')
print(mobj.group())
a

这里,很明显, []没有被匹配到,因为它们在正则表达式中的含义是匹配该括号里面的一个字符。假如你想要匹配 []的话,你需要在它们前面加\

import re
mobj = re.search(r'\[a\]', '[a]b')
print(mobj.group())
[a]b

group() 方法

group 方法用来返回一个或多个被查找到的匹配的子匹配字符串,它的引用方法如下,

group(index)

If you have a single argument in group function, the result will be a single string but when you have more than one arguments, then the result will be a tuple (containing one item per argument).

When there is no argument, by default argument will be zero and it will return the entire match.

When the argument groupN is zero, the return value will be entire matching string.

When you specify the group number or argument as a negative value or a value larger than the number of groups in pattern then IndexError exception will occur.

Consider the code below in which there is no argument in group function which is equivalent to group(0).

import re
str = "Working 6 hours a day"
mobj = re.match(r'^.*', str)
print(mobj.group())
Working 6 hours a day

Here group() is used and you have the entire matched string.

Picking parts of matching texts

In the following example, group function is used with arguments to pick up matching groups:

import re
a = re.compile('(p(q)r)s')
b = a.match('pqrs')
print(b.group(0))
print(b.group(1))
print(b.group(2))
pqrs
pqr
q

Here group(0) returns the entire match. group(1) will return the first match which is pqr and group(2) will return the second match which is q.

Named groups:

Using named groups you can create a capturing group. This group can be referred by the name then. Consider the example below:

import re
mobj = re.search(r'Hi (?P<name>\w+)', 'Hi Roger')
print(mobj.group('name'))
Roger

Non-capturing groups:

Non-capturing group can be created using ?:. Non-capturing group is used when you do not want the content of the group.

import re
mobj = re.match("(?:[pqr])+", "pqr")
print(mobj.groups())
()