我的Python学习日记
Date | Log |
---|---|
2023.12.09 | 完成编写 |
10. 正则表达式
前段时间在码字的时候,遇到一种情况需要把一段字符串中的括号及其中的内容去掉,以这两章目录为例:
Test_text = """* [2. Python,启动!](#启动)
* [2.1 标识符](#2.1)
* [2.2 输入输出](#2.2)
* [2.3 缩进](#2.3)
* [2.4 多行输入](#2.4)
* [2.5 注释](#2.5)
* [2.6 import](#2.6)
* [2.7 一些散碎关键字](#2.7)
* [3. 基本语句](#基本语句)
* [3.1 条件语句](#3.1)
* [3.2 循环语句](#3.2)
* [3.2.1 for循环](#3.2.1)
* [3.2.2 while循环](#3.2.2)
* [3.2.3 break](#3.2.3)
* [3.2.4 continue](#3.2.4)
* [3.3 pass语句](#3.3)
"""
如果在C语言的环境下,我肯定想去写一个算法解决这个问题,但是这就太麻烦了,Python就白学了,我决定问问ChatGPT,它说我们可以用正则表达式来解决这个问题,所以就有了本章的内容。(点我直接看答案)
什么是正则表达式?
正则表达式(Regular Expression),是一种用于匹配字符串模式的表达式。它是一种强大的工具,广泛用于文本处理、搜索和匹配操作。正则表达式提供了一种灵活的方式,使我们能够在文本中查找、替换或提取满足特定模式的字符串。
可以看到它的基本功能就是查找、替换或提取这三种了。
!!!在使用前要先import re
正则表达式的基本思想就是用一种特殊的语法来描述字符串的模式,比如我们上面的字符串Test_text
,我们想要去掉括号及其中内容,那么我们想要匹配到的模式就是:先匹配到左括号(
,接着匹配任意不是右括号)
的字符零次或多次,直到匹配到右括号,这样我们就描述了括号及其内容的模式。
基本思想了解了,那我们怎么用编程语言去表达这个模式呢?
答案就是通过使用元字符+普通字符辅佐以一些量词或边界符号来组合表达这个模式。
10.1 元字符
字符 | 描述 |
---|---|
^ | 匹配输入字符串的开始位置 |
$ | 匹配输入字符串的结束位置 |
\ | 将下一个字符标记为一个特殊字符 如我们上面要匹配左括号,那么就使用 \( 将左括号标记 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n} | 匹配确定的n次 |
{n,} | 匹配至少n次 |
{n,m} | n <= m,最少匹配n次最多匹配m次 |
x? | 其中x表示任何一个其他限制符(*, +, ?, {n}, {n,}, {n,m}) 此时匹配是非贪婪的,详见注1 |
. | 匹配除换行符之外的任何单个字符 |
(pattern) | 按pattern模式匹配并获取这一匹配,并将该匹配存放在类re.Match中 存放内容有索引范围span和匹配字符串match[注2] |
(?:pattern) | 按pattern模式匹配但不获取匹配结果[注3] |
(?=pattern) | 正向肯定预查,相当于是检查括号前的字符串是否能够正确匹配, 括号前的字符串匹配后,如果后面跟的字符串与pattern匹配, 则括号前内容属于正确匹配 |
(?!pattern) | 正向否定预查,也是检查能否正确匹配, 与上面的区别是后面字符串必须不与pattern匹配 |
(?<=pattern) | 反向肯定预查,与正向肯定预查类似,只是反向 |
(?<!pattern) | 反向否定预查,同上,例程见[注4] |
x|y | 匹配x或y |
[xyz] | 匹配所包含的任意字符 |
[^xyz] | 匹配未包含的任意字符 |
[a-z] | 匹配a-z中任意小写字符 |
[^a-z] | 匹配不在a-z中的任意字符 |
\b | 匹配一个单词边界,也就是单词和空格间的位置,见[注5] |
\B | 匹配非单词边界,结合上面例程中搜索ar在单词中间的例子, 第四项还可以用 \b\w*\Bar\B\w* 这个表达式 |
\w | 匹配字母、数字、下划线,等价于[a-zA-Z0-9_] |
\W | 匹配非字母、数字、下划线 |
\d | 匹配一个数字字符,等价于[0-9] |
\D | 匹配一个非数字字符 |
\n | 换行符 |
\r | 回车符 |
\s | 空白字符,等价于[\f\n\r\t\v] |
\S | 非空白字符 |
\p{Han} | 匹配中文字符,但需要安装并导入库regex |
表注
- 注1: 非贪婪,也就是尽可能少的匹配搜索的字符串,对于字符串"AHHHHHHHHHHHH!!!",
H+?
将匹配单个H,,而H+
将匹配所有H - 注2:
import re text = "Hello, my email is john.doe@example.com" # 使用捕获组获取邮箱中的用户名和域名 match_result = re.search(r'(\w+)\.(\w+)@(\w+\.\w+)', text) print(match_result) # <re.Match object; span=(19, 39), match='john.doe@example.com'> print(type(match_result)) # <class 're.Match'>
- 注3:
import re text = "apple banana cherry" # 使用非捕获组匹配水果名称,但不获取匹配 match_result = re.findall(r'\b(?:apple|banana|cherry)\b', text) print(match_result) # ['apple', 'banana', 'cherry'] # 用re.findall返回所有匹配结果,是一个列表
- 注4:
import re text = "Windows10" # 1. 正向肯定预查(Positive Lookahead) positive_lookahead_pattern = re.compile(r'Windows(?=10)') result_positive_lookahead = positive_lookahead_pattern.findall(text) print("正向肯定预查:", result_positive_lookahead) # 正向肯定预查: ['Windows'] 在搜索到Windows后,检查发现后面的字符串也是10,正确匹配 # 2. 正向否定预查(Negative Lookahead) negative_lookahead_pattern = re.compile(r'Windows(?!10)') result_negative_lookahead = negative_lookahead_pattern.findall(text) print("正向否定预查:", result_negative_lookahead) # 正向否定预查: [] 在搜索到Windows后,检查发现后面的字符串是10,被否定掉 # 3. 反向肯定预查(Positive Lookbehind) positive_lookbehind_pattern = re.compile(r'(?<=Windows)10') result_positive_lookbehind = positive_lookbehind_pattern.findall(text) print("反向肯定预查:", result_positive_lookbehind) # 反向肯定预查: ['10'] 在搜索到10后,检查发现前面的字符串是Windows,正确匹配 # 4. 反向否定预查(Negative Lookbehind) negative_lookbehind_pattern = re.compile(r'(?<!Windows)10') result_negative_lookbehind = negative_lookbehind_pattern.findall(text) print("反向否定预查:", result_negative_lookbehind) # 反向否定预查: [] 在搜索到10后,检查发现前面的字符串是Windows,被否定掉
- 注5:
import re text = "pillar regular arrange arch article march" word_beginwith_ar = re.compile(r'\bar\w*\b') word_endwith_ar = re.compile(r'\b\w*ar\b') word_endorbeginwith_ar = re.compile(r'\bar\w*\b|\b\w*ar\b') word_contain_ar = re.compile(r'\b\w+ar\w+\b') word_beginwith_ar_result = word_beginwith_ar.findall(text) word_endwith_ar_result = word_endwith_ar.findall(text) word_endorbeginwith_ar_result = word_endorbeginwith_ar.findall(text) word_contain_ar_result = word_contain_ar.findall(text) print(f"以'ar'开头的单词有:{word_beginwith_ar_result}") print(f"以'ar'结尾的单词有:{word_endwith_ar_result}") print(f"以'ar'开头或结尾的单词有:{word_endorbeginwith_ar_result}") print(f"中间包含'ar'但边缘没有的单词有:{word_contain_ar_result}") # 以'ar'开头的单词有:['arrange', 'arch', 'article'] # 以'ar'结尾的单词有:['pillar', 'regular'] # 以'ar'开头或结尾的单词有:['pillar', 'regular', 'arrange', 'arch', 'article'] # 中间包含'ar'但边缘没有的单词有:['march']
例:匹配邮箱的正则表达式:
import re
text = "Hello, my email is john.doe@example.com"
address_pattern = re.compile(r'\b[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,6}\b')
address = address_pattern.findall(text)
print(address) # ['john.doe@example.com']
10.2 查找
10.2.1 re.match
基本语法:re.match(pattern, string, flags=0)
- pattern 为匹配的正则表达式
- string 为要匹配的字符串
- flags 为标志位,可选,用于控制正则表达式的匹配方式,如是否区分大小写,多行匹配等,见10.5 修饰符
- re.match从字符串的起始位置开始匹配,如果起始位置就不匹配,直接返回None,如果匹配,就输出匹配到的内容,当然这个内容还是以re.Match存储
10.2.2 re.search
相比于re.match只能匹配字符串开头,re.search会扫描整个字符串并返回第一个匹配到的字符串
基本语法:re.search(pattern, string, flags=0)
- 参数意义与re.match相同
这里介绍一个函数group(index)
,可以将捕获的内容以字符串形式返回,
但在此之前我们要先明确捕获组的概念:当我们在规定正则表达式时,可以使用()
在正则表达式中划分捕获组,例如(\b\w*ar\b)(\s*)(\bar\w*\b)
划分了连续的三个捕获组,以 ar 结尾的单词、若干空白字符与以 ar 开头的单词,这样我们就可以使用group()
函数来访问这些捕获组了
import re
text = "pillar regular arrange arch article march"
s = re.search(r'(\b\w*ar\b)(\s*)(\bar\w*\b)',text)
if s:
for i in range(1,s.lastindex+1):
print(s.group(i))
print(s.group()) # regular arrange
else:
print("No match!")
比如这个例子,group()会将全部匹配项列出,而group(i)会将第i个捕获组输出
10.2.3 re.compile
re.compile函数用于编译正则表达式,生成一个正则表达式对象,提高模式的重用性
基本语法:re.compile(pattern, flag)
- 参数意义与上同
比如上面的例子可以改为:
import re
text = "pillar regular arrange arch article march"
s_pattern = re.compile(r'(\b\w*ar\b)(\s*)(\bar\w*\b)')
s = re.search(s_pattern,text)
if s:
for i in range(1,s.lastindex+1):
print(s.group(i))
print(s.group()) # regular arrange
else:
print("No match!")
10.3 替换
10.3.1 re.sub
re.sub可以替换字符串中的匹配项
基本语法:re.sub(pattern, replace, string, max, flags=0)
- pattern 为待替换的字符串模式
- replace 为用于替换的字符串
- max 为最大替换次数,可选,默认为最大
如果想要删除某些内容只需要用于替换的字符串为''
即可
所以我们现在已经可以解决开头时的问题,删除掉括号及其中内容了:
import re
Test_text = """* [2. Python,启动!](#启动)
* [2.1 标识符](#2.1)
* [2.2 输入输出](#2.2)
* [2.3 缩进](#2.3)
* [2.4 多行输入](#2.4)
* [2.5 注释](#2.5)
* [2.6 import](#2.6)
* [2.7 一些散碎关键字](#2.7)
* [3. 基本语句](#基本语句)
* [3.1 条件语句](#3.1)
* [3.2 循环语句](#3.2)
* [3.2.1 for循环](#3.2.1)
* [3.2.2 while循环](#3.2.2)
* [3.2.3 break](#3.2.3)
* [3.2.4 continue](#3.2.4)
* [3.3 pass语句](#3.3)
"""
del_bracket_pattern = re.compile(r'\(.*\)') # 删除所有括号及其中内容
del_enter_pattern = re.compile(r'\s') # 删除所有空白符
result_del_bracket = del_bracket_pattern.sub('',Test_text)
result_del_enter = del_enter_pattern.sub('',result_del_bracket)
print(result_del_enter)
# *[2.Python,启动!]*[2.1标识符]*[2.2输入输出]*[2.3缩进]*[2.4多行输入]*[2.5注释]*[2.6import]*[2.7一些散碎关键字]*[3.基本语句]*[3.1条件语句]*[3.2循环语句]*[3.2.1for循环]*[3.2.2while循环]*[3.2.3break]*[3.2.4continue]*[3.3pass语句]
10.4 提取
10.4.1 re.findall
基本语法:re.findall(pattern, string, flags=0, end)
- pattern 、 string 、flags 与上同
- end 为指定字符串结束位置,可选,默认为字符串长度
re.findall可以找到正则表达式所匹配的所有子字符串,并返回一个列表,如果有多个匹配模式,则返回元组列表
import re
text = "pillar regular arrange arch article march"
s_pattern = re.compile(r'(\b\w*ar\b)(\s*)(\bar\w*\b)')
s = s_pattern.findall(text)
if s:
print(s) # [('regular', ' ', 'arrange')]
else:
print("No match!")
10.4.2 re.finditer
和findall类似,但是finditer以一个迭代器返回
import re
text = "pillar regular arrange arch article march"
s_pattern = re.compile(r'(\b\w*ar\w*\b)')
s = s_pattern.finditer(text)
"""输出所有带'ar'的单词"""
if s:
for matchs in s:
print(matchs.group())
else:
print("No match!")
10.4.3 re.split
split 方法以匹配的字符串为分割符将字符串分割后返回列表
基本语法:re.split(pattern, string, max, flags=0)
- max 为最大分割次数,可选,默认为不限次数
import re
text = "pillar regular arrange arch article march"
s_pattern = re.compile(r'\s')
s = s_pattern.split(text)
"""以空白字符为分隔符将字符串分割"""
if s:
print(s) # ['pillar', 'regular', 'arrange', 'arch', 'article', 'march']
else:
print("No match!")
10.5 修饰符
修饰符就是我们上面一些函数中的flag位,用来控制匹配的模式
修饰符 | 描述 |
---|---|
re.INGORECASE 或 re.I | 忽略大小写 |
re.MULTILINE 或 re.M | 多行匹配 此时 ^ 和 $ 匹配每一行的开头结尾 |
re.DOTALL 或 re.S | 使. 匹配任意字符 |
re.VERBOSE 或 re.X | 忽略空白和注释 |
修饰符可以组合使用,用|
隔开,如re.X|re.I