python 正则大法好
PythoN 正则 官方介绍:
https://docs.python.org/3.2/library/re.html
https://docs.python.org/zh-cn/3.8/library/re.html
-
怎么在 有 多个 分组的情况下,拿到整个的匹配表达式(以这个功能切入)
最近使用分组功能,有点问题,还是源于对正则的不熟悉
例如下面的:
(https?|ftp|file)://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})
我想匹配一个硬编码的东西,像上面这样,我想 匹配https 或者 ftp file 之一的,那我就的 用 | 匹配左边或者右边。如果不止一个 左边或者右边,那就需要几个 | 一起组合,那我就得加个 (|) xxx(|),但是问题是,加了 () 他就看做是一个分组,使用 match 和 search 都还好,可以使用 group(), 拿到 整个匹配的东东西,但是万万一使用findall 就不行了,findall 会把只会把你的分组变成一个元祖(一个分组,就直接是那个分组的字符串,多个分组就是每个分组组成的元祖,没有分组,就是整个的匹配 下来的字符串),只有不用分组regx 里面不用 ()
,findall 才可以拿到,整个字串。
其实 re 给我们考虑到了 这个问题。看下面的操作把
先来看看 match search和 findall 对于分组的处理都是什么样的把
match
print(re.match(r"(aa.*?aaa)", "aafgdfaaa").groups())
>>('aafgdfaaa',)
print(re.match(r"(aa.*?aaa)", "aafgdfaaa").group())
>> "aafgdfaaa"
print(re.match(r"aa.*?aaa", "aafgdfaaa").group())
>>"aafgdfaaa"
print(re.match(r"((aa).*?(aa))(a)", "aafgdfaaa").groups())
>> ('aafgdfaa', 'aa', 'aa', 'a')
re.match(r"(((aa).*?(aa))(a))", "aafgdfaaa").groups())
>>('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')
search
print(re.search(r"(((aa).*?(aa))(a))", "aafgdfaaa").groups())
>>('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')
search 和 match 都差不多,无非是 match 默认加了个 ^ 开头匹配。
使用 group(index=0) 可以取到 整个匹配表达式(不管你最外面有木有 ( )
有的话, group(0) == group(1)== groups()[0]) 和 其他分组排列下来
ps: 不加 ( )
groups() => () 为空,但是 group(0) 却可以取出整个匹配的表达式
findall
print(re.findall(r"(aa).*?(aa)", "aafgdfaaa"))
>>[('aa', 'aa')]
print(re.findall(r"((aa).*?(aa))", "aafgdfaaa"))
>>[('aafgdfaa', 'aa', 'aa')]
print(re.findall(r"((aa).*?(aa))(a)", "aafgdfaaa"))
>>[('aafgdfaa', 'aa', 'aa', 'a')]
print(re.findall(r"(((aa).*?(aa))(a))", "aafgdfaaa"))
>>[('aafgdfaaa', 'aafgdfaa', 'aa', 'aa', 'a')]
可以看到,findall 其实是把 ()
当做分组, 遵守从外到内,从前到后的原则,所以有多个()
嵌套的,findall 列表的元素就有几个,类似于 一个图形里面有几个三角形,顺序是外面的优先,然后是他的子级,同一层级,从前到后排列。(深度优先)(其实match 和search 的 group groups都是这种逻辑)
findall 只是返回一个 groups (有分组,优先) / group(0) ( 无分组) 的 元素的列表(这里就和group 没关系了)。
所以第一种方法: 可以findall 匹配整个表达式的是 可以 使用一个最大的 ()
然后取到 结果列表每个元素的第一个值(每个元祖的 第一个就是最大的那个 ()
的分组)
第二种方法就是利用 正则里面的 ?
关于 ?可能打部分人的第一印象是 非贪心, 例如:
举两个官方文档的例子:
1 、Causes the resulting RE to match 0 or 1 repetitions of the preceding RE
. ab? will match either ‘a’ or ‘ab’
2、{m,n}?
the 6-character string 'aaaaaa', a{3,5} will match aaaaa
characters, while a{3,5}? will only match aaa
characters.
所以 ? 第一作用是限制匹配的长度,拿到最短的,而且符合条件的字串
但是 ? 还有 另外一个作用 :
(?…) ... 是任意的正则表达式, 这种扩展通常并不创建新的组合; (?P<name>...) 是唯一的例外
? 后面的一个字符,代表了 这个"分组"的处理规则。
比如:
(?:…) ? 后面加个: 就表示非捕获组,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用
例如:
text2 = '''/* this is a
... multiline comment */
>>> comment = re.compile(r'/\*((?:.|\n)*?)\*/') # 这里使用?: 来穿件一个非捕
获组。匹配. 或者 \n ( 因为 . 不能匹配 \n) 所以findall 元素只是一个 最外面的分组。
>>> comment.findall(text2)
[' this is a\n multiline comment ']
-
re 里面的
|
这个符号再解释一下吧,就是 匹配左边或者 右边的
单个字符请使用[xx]A | B(其中A和B可以是任意RE)会创建一个匹配A或B的正则表达式。任意数量的RE都可以由 '|'分隔 通过这种方式。也可以在组内部使用。 扫描目标字符串时,RE用 “ |”分隔 从左到右尝试。当一个模式完全匹配时,该分支被接受。这意味着一旦一个比赛,乙将不会进一步测试,即使它会产生一个较长的整体搭配。换句话说, “ |” 操作员从不贪婪。匹配文字 “ |” ,使用 \ | ,或将其包含在字符类中,如 [|]所示
idea 智能提示,如果只是一个字符,使用[ xx ] 比较快哦
re.match(r"aa(?:fg|g).*?aaa", "aafgdfaaa")
两个字符就不会有提示了,哈哈
下面附上我的re 官方文档阅读笔记:
普通字符串 比如: 都是着正则特殊含义的字符串
要匹配一个反斜杠字面值,用户可能必须写成 "\\\\" ,
因为 "\\",而每个反斜杠在普通 Python 字符串字面值中又必须表示为 r"\\"。
以前我就遇到过,这个问题?
比如匹配 ' 就要用 \' 这种特殊形式 或者 * 使用 \* 来分别匹配 ' 或者 * 它本身
但是有时候 \ 匹配他自己,就需要 \\ ,有时候写起来比较麻烦,这时候,需要使用 r""。
在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 r"\n" 表示包含 '\' 和 'n'。
import re
# re.search("\[\]\\", 'ffggfh[]\\')
# 这时候,search 第一个参数 不加 r,就会报错 因为 \\ 字面为 一个 \ ,
# 等匹配的时候。就会发现,这个字符串右边是 ",不是一个合格的字符串了。
re.search(r"\[\]\\ ", 'ffggfh[]\ ') # 这样写就不会报错了,因为即使不加 r 第一个参数匹配的时候,已经是一个 \ ,
# 但是他的后面是个 空格, 这样最多是匹配不到
# 而第二个参数,是默认要匹配的字符串,如果单独一个 \ ,他会变成 \\ 传到下一级进行匹配。
# 无非是 py 字面字符串的一个完善,但是这种做法,会迷惑初学者
<re.Match object; span=(6, 10), match='[]\\ '>
'ffggfh[]\\'
# 'ffggfh[]\\'
r"ffggfh[]\\"
# 'ffggfh[]\\\\'
'ffggfh[]\\\\'
正则可以拼接
如果 A 和 B 都是正则表达式, 那么 AB 也是正则表达式
正则有普通字符,和特殊字符
普通字符,比如: abcds, f121, 等
特殊字符吧比如 | . * ? ( { ? 等,他们都会影响整个表达式的匹配规则。 匹配他们本身请用 \ 转义他们
比如:(?:a{6})* 匹配6个 'a' 字符重复任意次数, 同时这里使用了 ?: 这就把当前的() 变为不产生分组,而仅仅是 为了表达式本身运算的优先级了。
特殊字符的每个含义
1、 匹配除换行符 \n 的任意字符,如果要匹配所有字符,请使用 标志 re.S 或者 re.DOTALL
2、^ 匹配开头,MULTILINE 模式也匹配换行后的首个符号 (MULTILINE意思是多行,每一行首都可以加 ^ ,来表示)
re.match(r"^xxxa\n^bb", "xxxa\nbb", re.MULTILINE).group() # 第一参数中的第二个^ 其实写不写都行,因为他仅仅是表示 下一行的行首。
'xxxa\nbb'
3、} 默认到最后一个结尾,不管是不是 \n ,但是MULTILINE 把每个换行符都看做一个结尾了)
区别看下面的例子
print(re.findall(r"foo.$", "foo1\nfoo2\n"))
re.findall(r"foo.$", "foo1\nfoo2\n", re.MULTILINE)
['foo2']
['foo1', 'foo2']
4、 匹配0 或 多次, 尽可能多匹配,所以 贪吃
ab* 会匹配 'a', 'ab'
不解释,很简单
5、 匹配 至少 1 次,也是贪吃
re.match(r"ab+","abbbb")
<re.Match object; span=(0, 5), match='abbbb'>
5、 匹配最多 1 次,非贪吃,最多一次怕噎死啊
当然 ? 还有跟多有意思的用法,下面在看
6、 匹配前一个字符 m 次,不多不少
7、 匹配 m 到 n 次,也是贪吃啦
8、 {m,n}? 非贪吃 满足 m n 范围,尽可能少吃
'aaaaaa', a{3,5} 匹配 5个 'a' ,而 a{3,5}? 只匹配3个 'a'
9、\ 转义特殊字符
允许你匹配 '*', '?' 他们本身
10、 匹配 一个字符的集合
[amk] 匹配 'a', 'm' 'k'
字符范围,通过用 '-' 将两个字符连起来。比如 [a-z] 将匹配任何小写ASCII字符
[0-5][0-9] 将匹配从 00 到 59 的两位数字
[0-9A-Fa-f] 将匹配任何十六进制数位
ps : [a-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-] - 就只是表示他自己了
注意 特殊字符在 []会变成 普通字符了,所以 [(+)] 表示 匹配 '(', '+', '', or ')'。 所以我们可以用 [.]来代替 . 了
注意: \w 或者 \S 这种在集合内依然是有特殊含义的,不会匹配他们本身的。 和单个字符不同
使用 ^ 获取,不在 [] 里面的东西, ^ 必须是 [^] 第一个字符,否则他就是普通的字符
[^5] 将匹配不是 '5'的一个字符
11 、| 匹配 左边或者 右边
A|B 匹配 A 或者 B
任意个正则表达式可以用 '|' 连接。它也可以在组合(见下列)内使用
想要 自由组合请写在 (?:xx|xx) 这样就不会把 () 变成一个分组了,而仅是一个优先级的运算符了
12、(...) 分组 可以最长见 group 和groups 取值
可以在之后用 \number 转义序列进行再次匹配,见下列 \number
13 、\number 匹配数字代表的组合。
每个括号是一个组合,组合从1开始编号。比如 (.+) \1 匹配 'the the' 或者 '55 55', 但不会匹配 'thethe' (注意组合后面的空格)
re.match(r"(.+) \1", "th th").group()
'th th'
14 、(?…) 这是个扩展标记法
'?' 后面的第一个字符决定了这个构建采用什么样的语法。这种扩展通常并不创建新的组合
(?P<name>...) 是唯一的例外
(?aiLmsux) 匹配 ( 'a', 'i', 'L', 'm', 's', 'u', 'x' 中的一个或多个模式) 这里的几个字符都是有特殊含义的好吗。(最开始我还以为是简单的字符呢)(这个只是写在正则开头,对整个表达式生效, 单个模式请直接,用下面的模式写就可以了,这个只是方便多个模式写起来简单点, 不想对整个表达式起作用,请用 (?aiLmsux:xxxx)这杨就只是对 xxxx 起作用了)
re.A (只匹配ASCII字符)
re.I (忽略大小写)
re.L (语言依赖), re.M (多行模式), re.S ( . 匹配全部字符)
re.U (Unicode匹配)
re.X (冗长模式)
下面方式只覆盖组合内匹配,括号外的匹配模式不受影响。
例如:
(?a:...) 切换为 只匹配ASCII
15、(?:…) 正则括号的非捕获版本 (这个比较重要,所以单独写出来)、
匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用
这个已经试过很多次了
16、(?P<name>…) 这个也比较常见 为了复用,匹配的分组 (妈的,这个试了好像只能用作分组的模式)
这个和 \number 很像 每个组合名只能用一个正则表达式定义,只能定义一次, 没有名字的组合就是 默认\数字 代替那个组合
(?P<quote>['"]).*?(?P=quote)匹配单引号或者双引号括起来的字符串
分组名是同一个的,group 取值的时候只算做一个
print(re.match(r'(?P<quote>[;]).*?(?P=quote)', ";asfdfm;").group(1))
re.match(r'(?P<quote>[;]).*?(?P=quote)', ";asfdfm;").group()
;
';asfdfm;'
17、(?=…) 前判断性质的匹配,当xx 后面是 ... 才匹配 xx
Isaac (?=Asimov) 匹配 'Isaac ' 只有在后面是 'Asimov' 的时候
18、(?!…) 17 取反, 当xx 后面不是 ... 才匹配 xx
19、(?<=…) 后判断性质匹配 xx的前面匹配 … 的内容, 才 取到 xx
(记法 < 嘴对准右边,说明是右边是要吃到的匹配字符,所以在后面是我们需要的东西,左边是判断依据)
(?<=abc)def 会在 'abcdef' 中找到一个匹配
注意:
匹配样式...必须是定长的
abc 或 a|b 正确
a* 和 a{3,4} 不正确
print(re.search('(?<=abc)def', 'abcdef'))
re.search(r'(?<=-)\w+', 'spam-egg') # 匹配 -后的一个单词
<re.Match object; span=(3, 6), match='def'>
<re.Match object; span=(5, 8), match='egg'>
20、(?<!…) 19 取反
21、(?(id/name)yes-pattern|no-pattern)
这个比较绕,总结来说根据是不是已经匹配了某个组来匹配 yes-pattern
或者不匹配指定分组时匹配 no-pattern
yes-pattern : 如果有匹配到的group-id/group-name 就匹配 yes-pattern
no-pattern : 如果没有匹配到 group-id/group-name 就匹配 no-pattern (可以不写no-pattern, 就默认 "空" 喽)
不匹配:(我在 group 后加了 ?,这样为了更好的演示)
import re
a = r"this is a test end"
b = r"(?P<first_group><)?this is a (?(first_group)test|test end)"
re.search(b, a).group()
>> 'this is a test end'
匹配:
import re
a = r"<this is a test end"
b = r"(?P<first_group><)?this is a (?(first_group)test|test end)"
re.search(b, a).group()
>> '<this is a test'
这个感觉用的蛮多的喽
一下是一些简单的,那就简单说一下喽
21、\d 相当于 [0-9]
22、\D \d 取非
23、\s [ \t\n\r\f\v]
24、\S \s 取非
25、\w 数字字母下划线,中文等
26、\W \w 取反
一下是一些简单的标志位,上面已经差不多都讲过了
回顾一下
re.A 只匹配 ASCII
re.S 让. 配 \n
re.I 忽略大小写
re.M 多行模式 主要是 ^ 和 $