《精通正则表达式》读书笔记
2022-04-27 12:27:11 59 举报
AI智能生成
历时2个月,整理的正则表达式学习笔记。
作者其他创作
大纲/内容
例子
没有指定re.M 多行匹配的模式,^.*$并没有匹配结果。
如果要匹配所有行。
['这是第一行,']
如果匹配“这”字开头的行
['here is 第四行']
如果匹配“行”字结尾的行。
search 扫描整个字符串,并返回第一次匹配的位置。
如果表达式中不含有^$起止符的话,匹配所有行。
findall
注意二:注意re库中 search match fullmath findall之间的差异。
^和$ 定位一行的开头和一行的结尾
匹配一个未列出的字符。
[^A]
^排除字符
<[hH][123456]>
<[hH123456]+>也能匹配,但是像<h1234>这种不符合要求的HTML标签也会被匹配到。
例如:查找所有的H标签
匹配a到z小写字母的任意一个。
[a-z]
如果-位于首位,则当做普通字符。
-连字符
注意两个字符
[ ] 字符组:匹配字符组里的一个字符
. 匹配任意一个字符
(a|b)匹配a或者b,a和b是独立的正则表达式。
结果:
例,找出html文件中所有的link标签和script标签里内容。
| 多选结构
可以匹配color,colour。但是不能匹配colouur。
结果
colou?r
如匹配color、colour。
4(th)?
匹配4和4th
? 出现0次或者1次
子主题
colou+r
比如color和colour的匹配
提取所有的养殖项目和规模。
s = \"养鸡50只,养鸭70只,羊5头,龙虾5亩\"
+ 出现1次或者多次,最少出现一次
link后不管有没有空格都可以匹配。
思考:很明显 <linkrel=\"\">这样的标签是错误的,如果要匹配合法的html标签可以用+,link\\s+ 至少出现一次空格才是合法的。
匹配html文件中的空格。
* “之前紧邻的元素出现任意多次,或者不出现也可以匹配”
第一位数字不能是0
[1-9]
后面5位可以是任何数字
[0-9]{5}
[12] 年份的第一位目前只能出现1和2
[09]年份的第二位目前只能出现0和9
[0-9]{2}年份的第三第四位可以是任意2位数字
年的匹配
如果月的首位为0,第二位可以出现1-9的任意数字
如果月的首位为1,第二位只可以出现0、1和2
(0[1-9]|1[12])
月的匹配
如果日的首位为0第二位可以出现1-9的任意字符。
如果日的首位为1第二位可以出现0-9的任意字符
如果日的首位为3,第二位只能出现0和1
([0][1-9]|[12][0-9]|[3][01])
日的匹配
年份的匹配
\\d{4}|\\d{3}[xX]{1}
\\d{3}[0-9Xx]{1}
两种方法
身份码的匹配
例:匹配合法的身份证号:[1-9][0-9]{5}[12][0-9][0-9]{2}(0[1-9]|1[012])([0][1-9]|[12][0-9]|[3][01])\\d{3}[0-9xX]{1}
font color=\"#fdb813\
量词:作用于之前紧邻的元素。
命名一个名为dup的表达式
?P<dup>/b/w+/b
命名组合:(?P<表达式别名>表达式)
重复了一次dup表达式,含义是查找拼写中出现了重词的情况。
(?P<dup>/w+)(?P=dup)
反向引用:(?P=表达式别名)
命名组合和反向引用
这里有三个括号结构如下:(()())
最外层的()
对应的有三个分组:
也可以使用[0]或者group(0) 来分别获取对应的分组。
\"([0-9]+(\\.[0-9]+)?([fFcC]))\"
设置了不捕获的分组。使用findall/search/match (?:)标记的分组,将不显示在结果里。
pat = \"([0-9]+(?:\\.[0-9]+)?([fFcC]))\"
(表达式) 捕获分组和不捕获分组(?:表达式)
顺序环视,从左到右查找文本,找到后标记位置,并以此位置为基础,通过表达式2两边查找。2-1左边匹配,2-2右边匹配。
表达式1:定位正则表达式。
顺序环视匹配所有的有单词边界的david,找到位置后再匹配。
例子1:
锚点两边匹配
例子2:
表达式2-1 2-2:在表达式1定位后,往前(2-1)或者往后(2-2)查找文本,匹配符合表达式的文本。
表达式2-1(?=表达式1)表达式2-2
(?=david)
(?<=david)
正序在匹配文本的左边,逆序在右边。
图示:
比较两个环视定位的位置差异
(\\d{3})+
以三个数字为一个单位进行编组,提高运行效率,该编组不需要被捕获(?:)
环视是逐个字符环视,符合条件的位置都会被标记,直到最后三个数字,倒数第二个和倒数第一个不符合条件不标记。
没有边界限定,结果是这样的。
结果是一样的
所以要给每三个数字加一个边界限定 \\b 或者$
(?=(\\d{3})+)
将这个作为环视条件
是这种情况
新问题出现:如果位数是三的倍数,首位也会被标记,这样不符合要求。
替换后的结果
从这个位置向前要有数字,所以用逆序环视(?<=\\d)
解决:这些位置要符合前面还有数字的条件。显然前面两个不符合。
能不能用\\d来限定有一位数字的条件?
思考:
思路:
综合案例:给数字千位加逗号。
逆序环视,从右到左查找文本,找到符合表达式1的文本,标记位置,可以在这个位置前后
表达式2-1(?<=表达式1)表达式2-2
环视
正则表达式初体验
字符串的构成
匹配流程
占有字符
匹配流程:
无论有多少子表达式,匹配结果是一样的。
零宽度
占有字符和零宽度:
匹配失败的情况
正则表达式:abc
控制权转换和指针移动
NFA引擎的工作原理
从字符串左侧开始
如果匹配成功,字符串和表达式指针前进1位,
依次,用c匹配字符c,匹配成功。字符串指针前进1位同时,表达式指针到达结束位置,输出匹配结果,abc
继续匹配剩余字符,正则表达式 abc 开始匹配后续字符 d
如果是abca是什么情况?
a 无法匹配 字符 d 匹配失败。
优先选择最左端的匹配结果
0或者1,优先匹配的是?量词的上限 1
1 或者 任意多,优先匹配的是+量词的上限 任意多
+ 相当于 {1,}
零 或者 任意多
* 相当于 {0,}
n到m次
标准量词
匹配成功后,输出匹配结果,然后,继续从,处继续匹配
先从 ,号开始,.* 会匹配所有字符,遇到 [0-9] 开始回溯寻找符合 [0-9] 的字符如果没有,上面箭头向前进1位,至 字符 则 的位置,再用 .* 匹配所有字符,再回溯寻找符合 [0-9] 的字符如此循环,直至文档结束。如果没有符合 [0-9] 规则的字符,匹配失败。
注意,如果字符串,变成以上情况,回溯至1处,匹配成功,输出匹配结果,与上面匹配结果不同。但原理相同, .* 会匹配所有字符,后面 [0-9] 会回溯,当首次找到符合 [0-9] 规则字符时,就返回结果。
.*[0-9] 的匹配流程【量词+字符】
流程: .* 会匹配所有字符,当遇到font color=\"#f1753f\
+ b style=\
.*(\\d+)
?相当于font color=\"#f1753f\
.*(\\d?)
* 相当于{0,},取下限0,回溯匹配,结果为空
.*(\\d*)
匹配下限2,结果2个字符
确定的量词,则会根据确定的数量匹配。
.*(\\d{4})
如果后面是不确定的量词,遵循的规则时,font color=\"#f68b1f\
font color=\"#f15a23\
标准量词的匹配流程
标准量词是匹配优先的
回溯的流程
如果,字符串是 \"Regex\"r 的话,回溯匹配的流程
用 \".*\" 匹配 “Regex”的过程
如图:回溯时从 .* 匹配的所有字符当中 ,从后面到前面依次匹配,当第一匹配成功,回溯匹配也就成功了。
例如:如果匹配“”内的内容,使用\".*\",并不能得到预期的效果。
匹配流程梳理可见,表达式“[^”]*”没有量词匹配失败回溯的过程,所以匹配效率比较高。
“[^”]*”
第一种思路:修改匹配规则
这个更为常用。因为使用了忽略优先量词*?,所以在需要匹配\".*?时,.*的状态会被忽略,继续匹配”,如果匹配成功就返回结果。
\".*?\"
第二种思路:非贪婪匹配【忽略优先量词】
如图((?!要排除的字符).)* 也可以匹配除了<b>以外所有的字符。
注意:与[^<b>]不同,这个会把<b>拆分成<、b、>三个字符来排除,而不是作为一个整体<b>来排除。
第三种思路:利用排除环视来去除指定字符
如何解决:
.* 的陷阱
回溯的概念
ab?c 匹配 abc 的流程
ab?c 匹配 ac 的流程
ab?c 匹配 abx的 流程
匹配优先量词算法: ab?c 分别匹配 abc ac 和 abx的过程
匹配优先算法 \".*\" 的局限
忽略优先算法 \".*?\" 可以克服以上局限
关于忽略优先量词的效率问题
忽略优先量词算法:\".*?\" 匹配:\"a\"\"b\"\"c\"
(\\.\\d\\d[1-9]?)\\d* 的匹配结果
(\\.\\d\\d[1-9]?)\\d+ 的匹配结果
解决思路:防止回溯。当 [1-9]? 匹配成功后就固化匹配结果,后续匹配不再回溯匹配
(\\.\\d\\d(?>[1-9]?))\\d+ 的匹配结果
固化分组的思路
^(?>\\w+):
如:如果没有固化分组,会一直尝试 \\w+:,回溯,再尝试,直到匹配到最后一个字符。
如果使用固化分组,\\w+会按照\\w+的要求匹配所有符合条件的字符,并锁定,继续往后匹配,不管成功还是失败,都不会执行回溯——尝试的步骤。这样会显著减少循环运行的次数,可以提高匹配效率。
使用固化分组的好处:提升匹配效率。
不过可以使用环视的功能,模拟固化分组。
^(?=(\\w+))\\1:
可以改造成
这样的表达式,结构清晰,但是要复杂一点
匹配一个单词的位置,并匹配这个单词\\1,\\1表示的是反向引用。也可以用
匹配是三位小数的例子
注意:python不支持固化分组功能
固化分组和回溯
回溯的原理
如:匹配每个月的日期
这称之为分支结构的陷阱
这样可能会匹配不出想要的结果!因为所有的数字都会符合第一个分支,抛出结果后匹配结束,后面的分支基本没有机会匹配。
修改方案一:如果遇到多个分支且都能匹配相同结果的情况时,把能匹配到最短的结构放到分支结构的最后。
进一步完善方案:33的解决方案。用单词边界\\b,甚至可以不用考虑分支结构的顺序了
规则:10以下 有09或者9,最大31。
需要慎重思考分支结构的顺序
问题一:分支结构的匹配是顺序匹配
问题二:引擎会按顺序匹配,不符合的匹配,会回溯到分支结构前。
需要考虑两个问题:
分支结构的原理
以上表达式为何会匹配失败?
肯定顺序环视:(?=表达式H)
否定顺序环视:(?!表达式H)
肯定逆序环视:(?<=表达式H)
否定逆序环视:(?<!表达式H)
匹配替换的流程
数字千位添加逗号
分析思路:
匹配HTML标签中的内容
环视小技巧
正则的匹配原理
^ 匹配一行的开始位置
$ 匹配一行的结束位置
行的边界
匹配的位置在 \\w和\\W之间,\\w开始或结尾的边界。注意以上的\\W
如果用顺序环视可以模拟\\b
\\b 匹配空字符串,或者字符【大小写字符。数字。下划线】边界的位置。【常用于匹配单词边界】
匹配以re打头的字符。
\\b的位置
\\B的位置
\\b和\\B的位置
\\B 匹配非字符边界的位置
单词的边界
只匹配文本开头的位置,而不是一行的开始位置,和^不一样!
一行开始的位置:^
\\A 匹配文本开头的位置
也是文本的结束位置,而不是一行的结束位置
\\Z 匹配文本结束的位置
文本的边界
如图,位置在匹配字符串的左边。
(?=表达式)肯定正序环视,根据表达式定位到符合表达式规则字符的左边位置。
如图,位置在匹配字符串的右边。
(?<=表达式) 肯定逆序环视,根据表达式定位到符合表达式规则字符的右边位置
刚好和(?=string)相反。除了指定的位置,其他位置都能定位。
可以用这个特性,截断字符。
简单粗暴的也不行
查找某个html标签里的内容,但是这个表达式也有弊端,如果有其他html标签的话,就无法匹配了。
正常的思路
所有<p>标签的左边的font color=\"#fdb813\
改进一下
完善一下:
意义:[^<>]只能匹配一个列表中的所列字符外的所有字符。
如果想匹配完美的效果,前置的表达式和否定正序环视的表达式是一样的
当否定正序环视匹配了相对应的位置后,需要用.来匹配任意字符,如果没有,只匹配位置,并为匹配字符,环视将失去意义,所以 . 非常重要。 当然其他字符也可以,原则是位置后一定要匹配一个符合要求的字符。
这里需要注意两点
如果想匹配多个字符串以外的所有字符,可以使用顺序否定环视的方法:表达式((?!表达式).)*
精巧的思路,用顺序否定环视 ((?!<p>).)*
匹配合法html标签里的内容。
匹配除了表达式左边的所有位置
否定正序环视的思考:
(?!表达式)否定正序环视,根据表达式定位符合表达式以外的位置。
(?<=string)的结果
匹配的位置刚好和(?<=string) 相反。
在前面的推演
在后面导致>后面的字符无法匹配,最终只能匹配标签本身,后面的字符无法匹配
使用否定逆序环视也可以实现类似排除一组字符的匹配结果,与正序不一样的是 . 要放在(?>!表达式)的前面
(?<!表达式)否定逆序环视,匹配(?<=表达式)以外的所有位置。
任意位置
表示位置的字符
. 匹配任意字符
指定了re.A的匹配模式,\\w不能匹配中文
匹配所有的unicode字符中的一个。如果在模式中设置了ascll标志,则只能匹配[a-zA-Z0-9_]
默认支持中文字符的匹配
除非特别设置,python默认匹配所有unicode字符,包括中文。
\\w 匹配一个字符,包括unicode大部分字符、数字和下划线_
\\W 匹配一个非字符【各种符号】
\\d 匹配任意数字
\\D 匹配任意非数字
\\t 制表符
\\v 垂直制表符
\ 换行符
\ 回车符
\\0 空字符
\\s 匹配任意一个空字符
\\S 匹配任意非空字符
英文字符
[a-zA-Z_]
非英文字符
[^a-zA-Z]
制表符、换行符、垂直制表符
[^\\t\\\v]
退格键
[\\b]
[ ] 匹配字符组内任意字符
正确的做法
[\"]在python中直接表示双引号是错误的,所以需要在前面加一个转义
\\ 转义
匹配一个字符
?
+
*
{m}
匹配最大值
{m.n}
匹配优先量词【贪婪匹配:尽可能多的匹配字符】
??
+?
*?
匹配最小值
忽略优先量词【非贪婪匹配:尽可能少的匹配字符】
具体原理:请参阅匹配原理中的匹配优先和忽略优先
量词
给捕获的组重命名。如果没有命名,匹配到的组都是以123……来区分的,但是如果指定了,那么符合匹配条件的字符,将被保存在指定名称(name)的变量中。
记录:(?P<name>表达式)
\\1
(?P=name)
反向引用的两种用法
用(?P<name>)和(?P=name)的方法
用\\1的方法
常见例子:找重复的词
捕获:(?P=name)
不记录捕获信息,这个对优化捕获结果,提高匹配效率费用有用,所以当遇到分组较多,()较多的情况时,可以考虑使用
不捕获:(?:表达式)
记录和捕获
| 分支语句
一个例子
确定的结果
不确定的路线
优化一下,就可以获取,确定时间的路线,和不确定时间的路线
思路:先匹配【路线】,在匹配【时间】,如果有【时间】字符串,向后匹配时间。如果没有,回溯到【路线】字符串后,获取没有时间的路线。
(?(1)yespattern|nopattern)
实际上语句2永远不会执行,因为只有(?P<判断>判断语句)匹配成功,才能继续向后执行语句。所以永远都是yes
所以要考虑(?P<判断>判断语句)没有匹配的情况,所以加一个?量词就可以了。(?P<判断>判断语句)?
一个注意事项:(?P<判断>判断语句)(?(判断)语句1|语句2)
?(某个分组表达式的结果)如果有结果就用表达式1匹配|如果没有结果就用表达式2匹配
条件和分支
(?a)匹配ascii,\\w只匹配英文、数字、_
(?u)匹配unicode,python默认匹配,所以\\w,也会匹配中文
(?L)匹配本地字符,慎用!
(?i)忽略大小写
(?m)多行匹配
默认.不匹配换行符,如果(?s)标记,.就可以匹配换行符
(?s)让.能匹配所有字符,包括换行符。
可以让匹配表达式可读性更好!
(?x)允许多行表达式
除了auL三个不能同时用,其他都可以组合使用
如:(?im)多行匹配和忽略大小写
组合使用
(?aiLmsux)
标记
python正则语法
如: font color=\"#fdb813\
注意:re.search没有位置参数,只要找到一个,就会返回结果,后面的即使是符合规则,也不会被匹配到。所以如果想匹配后面所有符合匹配规则的字符,就可以使用正则对象的search方法,使用递归函数的方式,匹配所有符合匹配规则的字符串。
检查整个字符串,寻找第一个符合匹配规则的字符,如果找到返回一个匹配对象,找不到返回None。
pattern.searchfont color=\"#ffffff\
看图
看代码
另一个例子:将key=value 形式的代码,转换成字典类型。
检查字符串开头,如果开头符合匹配规则,返回一个匹配对象,不符合,返回None
只有整个字符串符合匹配规则,才返回匹配对象,不符合返回None
使用match的弊端,超出11位的手机号码,也会被错误的识别。
使用fullmatch,则可以避免。
match和fullmatch,适合小单位字符串的匹配。比如匹配一个数字字符串是否是电话号码标准格式。fullmatch,则是精确匹配。
pattern.font color=\"#fdb813\
pattern.matchfont color=\"#ffffff\
先用pat匹配字符,然后根据匹配的字符,分割字符串。
\\W+匹配的结果
pattern.splitfont color=\"#ffffff\
修改后的代码,提取合法ip地址的代码
finditer()
findall()
与findall不同的是,findall返回的匹配文本的列表,finditer返回的是匹配对象的迭代器。
一个例子,去除html文件中的标签
比如:将将[p]标签转换为html标签。
其中repl可以是函数,在定义这个函数时,传入的是这个正则表达式所返回的正则对象
置换代码
关于sub中匹配的正则表达式:如果想同时匹配多个目标字符串,可以使用正则的分支语句。比如常见的置换文本。
例子:一共匹配了12次
返回正则表达式的匹配模式。
pattern.flags
返回一个正则表达式有有几个分组。
pattern.groups
如果表达式中有(?P<name>)的结构,则会返回一个字典,没有返回一个空字典
pattern.groupsindex
返回正则表达式
pattern.pattern
其他属性
正则对象的方法和属性
使用递归,逐个匹配。
匹配合法的ip地址
将表达式编译成一个正则对象
pattern = re.compilefont color=\"#ffffff\
m.group(组名或者组索引)
m['组名'] 或者 m[组索引]
返回所有匹配的组
如果某一个组没有匹配到结果,可以用default来指定。
default参数
m.groups(default=None)
返回所有匹配的组,返回的结果是一个字典。{组名:匹配的内容,}
注意:如果正则表达式中没有自定义组名,这个方法,将会返回一个空字典
m.groupdict(default=None)
返回匹配的内容
如果没有指定group参数,返回的全局匹配结果的开始和结束位置。
m.span(group)
返回指定group匹配开始位置和结束位置。
m.start(group) 和m.end(group)
m.pos和m.endpos
返回一个正则对象
m.re
返回string的内容,即被匹配的字符串。
m.string
m.lastindex()
返回最后一个匹配到组的名字。
m.lastgroup()
返回匹配对象的一些属性
扫描整个字符串,返回第一个符合正则表达式的结果,整个结果是一个匹配对象
search是一个找到即止的函数。
例子:用正则对象的search方法实现,类似finditer 的功能
第一次代码
查看
使用生成器函数,修正上面的代码
注意:search函数和正则对象的search方法的异同
match = re.font color=\"#fdb813\
search和match的比较,match只扫描字符串头部,如果是匹配确定格式的文本,match的效率要高于search
扫描字符串的头部【不是整个字符串!】:字符串的开始处就必须符合规则才会返回匹配结果,否则就返回None,即使第2个字符开始,就已经符合匹配规则。
也是一个也是找到即止的函数
精确匹配!从开始到结束的位置,必须都匹配才能返回匹配结果
match需要精确匹配头部
fullmatch则需要精确匹配头部和尾部
因为#的存在,不符合正则表达式,fullmatch没有结果。但是字符串头部满足匹配规则,有结果。
看一个例子
match和fullmatch的差异
扫描全部字符串,返回所有符合匹配规则的结果文本。
有括号的情况:
没有括号的情况,默认全局匹配
两者的区别
findall返回的是一个列表。列表的每个元素,是由表达式中的捕获组【由表达式中的()决定的】所定义的元组
results = re.font color=\"#fdb813\
扫描全部字符串,返回所有符合匹配规则的匹配对象
用递归函数和yield 写了一个类似finditer功能的函数
返回的是一个包含了所有匹配对象的迭代器
matchs = refont color=\"#fdb813\
根据正则表达式,寻找字符串中符合匹配规则的文本,然后基于找到的字符,将整个字符串拆分成一个列表。
如图:显然使用正则split,结果要准确的多!
比较两个split的差异
同str.split()函数,str中的split函数,可供分割的字符必须是固定的。功能没有re.split强大。
result = re.font color=\"#fdb813\
根据正则表达式,扫描整个文档,对于符合正则规则的文本,用指定的文本或者函数【repl参数】进行替换
代码
例子:扫描文件夹,找出所有的电子书文件,提取书名,并制作一个书单
比字符串replace要强大的多的字符串替换功能。
repl支持函数,让替换有了无限的可能。
功能同sub,不同的是subn 返回了替换的次数。用于查找软件中:提示【一共完成了XXX处替换】
也可以在表达式中使用(?i)来表示忽略大小写
re.I 忽略大小写
也可以在表达式中可以使用(?m)来表示
re.M 多行匹配
re.D 强化 . 的匹配范围
也可以在表达式中使用(?x)
re.X 支持多行正则表达式
re.A 表达式只对ascii编码起作用。如果匹配的是纯英文的字符串,可以考虑使用该模式
re.L 匹配本地语言
匹配标记:
re.purge 清除正则表达式缓存
返回一个正则错误对象
转义表达式中的特殊字符
应用场景,如果遇到需要用户输入的正则表达式时,如果表达式中有些字符需要转义,可以使用该函数,把用户输入表达式转义成合法表达式
re.escape(pattern)
其他功能
其他内容
re库的模块
python的正则库
正则表达式学习
0 条评论
回复 删除
下一页