前言

问:正则表达式 为何物?

简单来说,正则表达式 就是一套用于描述字符串排列的规则。通过正则表达式可以在任意的字符串中匹配出需要的结果。

在python语言中,通过调用 RE 模块 来实现正则表达式的使用。同时,其 re 也是一种小型、高度专业化的编程语言。

功能及原理:通过一些特定的字符组合成一条正则表达式,然后程序根据用户的设定,将其匹配到的内容(字符串)返回;反之,则不返回任何字符串(即过滤)。

正则表达式组成?

正则表达式大致由:原子、元字符、模式修正符和一些其它符号组成。

原子

原子是正则表达式中最基本的组成单位,每个正则表达式中至少要包含一个原子,常见的原子及其组合有以下几类:

  • 普通字符作为原子
  • 非打印字符作为原子
  • 通用字符作为原子
  • 原子表

(1)普通字符作为原子

使用普通字符作为原子,如:数字、大小写字母、下划线等。

例如:使用普通字符串"tai"作为原子使用,其组合就一共有 3 个小原子,即 tai

实例演示:

从指定字符串中匹配与"tai"相关的字符集。

import re
pattern = "tai"
string = "http://www.taitaiblog.com"
result = re.search(pattern, string)
print(result)

实例输出:

<_sre.SRE_Match object; span=(11, 14), match='tai'>

分析:第一行中,我们首先引入了正则表达式需要的 re 模块,第二行定义了一个普通的原子"tai"并将其赋值给 pattern变量,第三行设定一个需要待匹配的字符串,第四行则调用 re 模块中的 search()函数,通过该函数从string中匹配对应的正则表达式,有其值则返回对应的值,并赋给result变量,最终显示出来。

从输出结果可见,最终输出的是一个对象,并非实际匹配到的字符串,然而这并不影响,后面我们会讲如何通过对象取其中匹配到的值。

(2)非打印字符作为原子

所谓的非打印字符,指的是在编程中一些用于控制格式的符号。常见的,如:换行符 \n、制表符 \t 等等

实例演示:

import re
pattern = "\n"
string1 = """www.taitaiblog.com
www.csdn.net"""    # 有换行
string2 = "www.taitaiblog.comwww.csdn.net"    # 无换行

result1 = re.search(pattern, string1)   # 匹配 有换行 的字符串
result2 = re.search(pattern, string2)   # 匹配 无换行 的字符串

print(result1)
print(result2)

实例输出:

<_sre.SRE_Match object; span=(18, 19), match='\n'>
None

分析:从实例可以看出,定义"\n"非打印字符正则,可以迅速知道某个源字符串中是否含有对应的特殊字符。

(3)通用字符作为原子

所谓的通用字符 ,指的是使用一个原子可以匹配某一类的字符集的特殊字符,常见的通用字符及其含义如下表:

符号含义
\w匹配任意一个字母、数字或下划线
\W匹配除字符、数字和下划线以外的任意一个字符
\d匹配任意一个十进制数
\D匹配除十进制数以外的任意一个其它字符
\s匹配任意一个空白字符
\S匹配除空白字符以外的任意一个其它字符

实例演示:

import re
pattern = "\wpython\d\w"   # 定义通用字符正则
string1 = "ABCpython3_abc"   # 情况一
string2 = "123python2ABC"   # 情况二
result1 = re.search(pattern, string1)
result2 = re.search(pattern, string2)
print(result1)
print(result2)

实例输出:

<sre.SREMatch object; span=(2, 11), match='Cpython3* '>
<* sre.SRE_Match object; span=(2, 11), match='3python2A'>

分析:正则中除了定义普通字python符外,还加入了3个通用字符,作为组合正则,从输出结果可知程序均匹配到了相应的内容,具体的内容为 match 中的值。

(4)原子表

在python中,原子表用 [] 表示,其功能是:定义一组地位平等的原子。在匹配正则时,会自动的抽取其中的原子进行比对。如果正则中除了原子表 还有其它的原子 ,程序会自动的抽取原子表中的任一原子,并将其放到合适的位置,组合后再进行比对。

原子表匹配有两种情况:

(1)匹配原子表中含有对应字符的内容(即包含原子表内的元素)

(2)匹配除原子表中含有字符以外的内容(即不包括原子表内的元素)

匹配原子表中包含的元素

例如:定义正则 pattern = "[ABC]python",其最终可以匹配出以下可能结果:"Apython"、"Bpython"、"Cpython".

实例如下:

import re

pattern = "[ABC]python\d\w"

string1 = "Bpython3study"   # 匹配1
string2 = "Apython2learn"   # 匹配2
string3 = "Bpython3studyApython2learn"   # 匹配3-测试匹配优先顺序
string4 = "Dpython3study"   # 不匹配1

result1 = re.search(pattern, string1)
result2 = re.search(pattern, string2)
result3 = re.search(pattern, string3)
result4 = re.search(pattern, string4)

print(result1)
print(result2)
print(result3)
print(result4)

实例输出:

<*sre.SRE_Match object; span=(0, 9), match='Bpython3s'>
<* sre.SRE_Match object; span=(0, 9), match='Apython2l'>
<_sre.SRE_Match object; span=(0, 9), match='Bpython3s'>
None

分析:这里只分析 “匹配3” 的过程,在 pattern 中,定义的是 [ABC],其顺序是 "A" → "B" → "C",而在源字符串 string3 中,定义的两个内容均是匹配的,只是有先后顺序,"Bpython3study" 在前,"Apython2learn" 在后,而从输出结果第三行中可见首先匹配到的却是 "Bpython3study" ,其主要原因是 re 在匹配时,会优先的匹配目标字符串中首个合适的内容,并非根据原子表中原子出现的顺序进行匹配。说白了,就是要在目标字符串中找到第一个合适的内容。

元字符

元字符,指的是正则表达式中一些具有特殊含义的字符。比如某一元字符可以重复N 次前面的字符等。

常见的元字符及其含义如下:

符号含义
.匹配除换行以外的任意字符
^匹配字符串的开始位置
$匹配字符串的结束位置
*匹配0次、1次或多次前面的原子
?匹配0次或1次前面的原子
+匹配1次或多次前面的原子
{n}前面的原子恰好出现n次
{n,}前面的原子至少出现n次
{n,m}前面的原子至少出现n次,至多出现m次
()模式单元符

(1)任意匹配元字符

在众多的元字符中,可以使用“.”元字符来匹配一个除换行以外的任意字符。

实例如下:

import re
pattern = ".python..tudy"
string = "Apython3study"
result = re.search(pattern, string)
print(result)

实例输出:

<_sre.SRE_Match object; span=(0, 13), match='Apython3study'>

分析:可见一个元字符"."可以代替一个任意的字符!

(2)边界限制元字符

边界限制元字符有两个,分别为: ^$

  • ^ 元字符:用于匹配字符串的开始。
  • $ 元字符:用于匹配字符串的结束。

实例如下:

import re

# 行首匹配( ^ )
pattern1 = "^abc"
pattern2 = "^abd"

# 行末匹配( $ )
pattern3 = "dy$"
pattern4 = "ey$"

string = "abc_python_study"

result1 = re.search(pattern1, string)
result2 = re.search(pattern2, string)
result3 = re.search(pattern3, string)
result4 = re.search(pattern4, string)

print(result1)
print(result2)
print(result3)
print(result4)

实例输出:

<*sre.SRE_Match object; span=(0, 3), match='abc'>
None
<* sre.SRE_Match object; span=(14, 16), match='dy'>
None

分析:(略)

注意:使用该元字符时,注意元字符的定义位置,^ 应在字符的前面,$ 应在字符的后面。

(3)次数限定符

常见的次数限定符有:*?+{n}{n,}{n,m} 这几个,具体含义,参照上表即可!

各实例如下:

* 匹配前面原子出现 0 次或以上次数 元字符

import re

pattern = "hello*"     # 出现0次或以上次数

string1 = "hel"    # 不匹配
string2 = "hell"    # 匹配
string3 = "hello"
string4 = "hellooooo"

print(re.search(pattern, string1))
print(re.search(pattern, string2))
print(re.search(pattern, string3))
print(re.search(pattern, string4))

实例输出:

None
<*sre.SRE_Match object; span=(0, 4), match='hell'>
<* sre.SRE_Match object; span=(0, 5), match='hello'>
<_sre.SRE_Match object; span=(0, 9), match='hellooooo'>

? 指定前面原子可有可无状态 元字符

import re

pattern = "ab?c"     # 指定 b 可有可无状态

string1 = "ac"    # 匹配
string2 = "abc"    
string3 = "abbc"    # 不匹配

print(re.search(pattern, string1))
print(re.search(pattern, string2))
print(re.search(pattern, string3))

实例输出:

<*sre.SRE_Match object; span=(0, 2), match='ac'>
<* sre.SRE_Match object; span=(0, 3), match='abc'>
None

+ 匹配前面原子出现1次或以上次数 元字符

import re

pattern = "hello+"     # 指定 o 必须出现1次或以上次数

string1 = "hell"    # 不匹配
string2 = "hello"     # 匹配   
string3 = "hellooo"

print(re.search(pattern, string1))
print(re.search(pattern, string2))
print(re.search(pattern, string3))

实例输出:

None
<*sre.SRE_Match object; span=(0, 5), match='hello'>
<* sre.SRE_Match object; span=(0, 7), match='hellooo'>

{n}{n, }{n,m} 匹配自定义前面原子出现次数 元字符

import re

pattern1 = "ab{2}"     # 匹配 b 出现两次的字符
pattern2 = "ab{2,}"     # 匹配 b 出现2次或以上次数的字符
pattern3 = "ab{2,6}cd"     # 匹配 b 出现2到6次之间的字符

string = "abbbbbbcd"

print(re.search(pattern1, string))
print(re.search(pattern2, string))
print(re.search(pattern3, string))

实例输出:

<*sre.SRE_Match object; span=(0, 3), match='abb'>
<* sre.SRE_Match object; span=(0, 7), match='abbbbbb'>
<_sre.SRE_Match object; span=(0, 9), match='abbbbbbcd'>

分析:(略)

(4)模式选择符

模式选择符 | 与关系运算符 or 类似,即从多个选项中任意匹配其中一个。

import re

pattern = "python|java"
string = "abcjavalearnpython"
print(re.search(pattern, string))

实例输出:

<_sre.SRE_Match object; span=(3, 7), match='java'>

分析:可见优先匹配了字符串 string 中的 java 字符。

(5)模式单元符

模式单元符 (),主要功能是将 () 符内的原子组合成一个大的原子来使用,实例如下:

import re

pattern1 = "(bc)+"    # "bc" 组合出现 1次或以上次数
pattern2 = "bc+"     #  c 字符出现1次或以上次数

string = "abcbcbcbclearnpython"

print(re.search(pattern1, string))
print(re.search(pattern2, string))

实例输出:

<*sre.SRE_Match object; span=(1, 9), match='bcbcbcbc'>
<* sre.SRE_Match object; span=(1, 3), match='bc'>

分析:pattern1 以字符 bc 作为一个新的、单独的原子使用,而 pattern2 则是以字符 b 作为原子,所以程序最终如上输出。

贪婪模式与懒惰模式

在匹配过程中,有 贪婪模式 和 懒惰模式 之分:

  • 贪婪模式:其宗旨是尽可能的多地匹配。
  • 懒惰模式:其宗旨则是尽可能少地匹配。

实例如下:

import re
pattern1 = "p.*n"   # 贪婪模式
pattern2 = "p.*?n"    # 懒惰模式
string = "ABCpython2DEFpython3GHIpython4567"

result1 = re.search(pattern1, string)
result2 = re.search(pattern2, string)

print(result1)
print(result2)

实例输出:

<*sre.SRE_Match object; span=(3, 29), match='python2DEFpython3GHIpython'>
<* sre.SRE_Match object; span=(3, 9), match='python'>

分析:可见贪婪模式是尽可能的去匹配更多的内容,而懒惰模式则是尽可能少的去匹配内容(即就近原则)。

问:如何快速设置贪婪模式或懒惰模式?

答:贪婪模式直接使用 .* 符号组合即可,而懒惰模式使用 .*? 组合进行设置。

模式修正符

模式修正符,其宗旨是指在不改变事先定义好的正则表达式、不改变元字符的前提下,让其可以兼容一些其它含义上的结果匹配。即通过模式修正符可以对原正则表达式做出一些微小的功能调整。如:加入模式修正符 I,可以让表达式在匹配时不区分字母的大小写。

常见的模式修正符及其含义:

符号含义
I匹配时忽略字母大小写
M多行匹配
L做本地化识别匹配
U根据Unicode字符及解析字符
S使元字符"."匹配包括换行在内的所有字符
X使存在分行符的正则表达式,也能正确匹配(目的:让表达式逻辑更清晰)

实例如下:

import re

pattern = "python"   # 定义正则 1

string1 = "abcPythonStudy"      # "P" 为大写
string2 = """
hello world
study python
hello make
study java
hello python
learn end
"""

# 测试忽略大小写匹配
result1 = re.search(pattern, string1, re.I)   # 加入"re.I"参数

# 测试 多行匹配
result2 = re.search(pattern, string2, re.M)    # 加入 "re.M"参数

# 测试 元字符"."匹配包括换行在内的所有字符
pattern2 = "(python).+(python)"     
result3 = re.search(pattern2, string2, re.S)

print(result1)
print(result2)
print(result3)

实例输出:

<*sre.SRE_Match object; span=(3, 9), match='Python'>
<*sre.SRE_Match object; span=(19, 25), match='python'>
<_sre.SRE_Match object; span=(19, 60), match='python\nhello make\nstudy java\nhello python'>

分析:(略)

完整学习教程请访问Python3 入门教程——目录索引

最后修改:2022 年 06 月 07 日
如果觉得我的文章对你有用,请随意赞赏