Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1744234
  • 博文数量: 391
  • 博客积分: 8464
  • 博客等级: 中将
  • 技术积分: 4589
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-13 15:12
个人简介

狮子的雄心,骆驼的耐力,孩子的执著!

文章分类

全部博文(391)

文章存档

2023年(4)

2018年(9)

2017年(13)

2016年(18)

2014年(7)

2013年(29)

2012年(61)

2011年(49)

2010年(84)

2009年(95)

2008年(22)

分类: Python/Ruby

2023-09-03 10:24:10

python正则表达式详解

正则表达式的用法较多,也比较灵活。网上的资料不是很全面。自己之前也只学了一点点,这里复习一下,并按照官方文档做一个总结,按照python3.10归纳。

本文按照下面的顺序讲解。

  • Pattern对象(正则表达式对象)
  • r前缀和\的意义。
  • 特殊符号的意义。
  • 序列功能
  • flags属性(匹配模式)
  • .search()和.match()
  • re.Match object对象(匹配结果对象)
  • 小括号的其他功能:内联标记、扩展标记法、分组
  • 其他函数
  • Pattern的额外属性
  • re.Match的额外方法


一、正则表达式对象 Pattern

re.compile()是用于创建一个正则表达式【Pattern】对象。这个对象就支持了正则匹配的所有方式。是一个不可变类型。

所以,正则匹配一般有两种方式

  • 直接使用正则对象的方法。==> 不需要指定正则表达式,本身就是正则表达式。
  • 或者使用re模块的函数。==> 需要指定正则表达式

#直接使用正则对象的方法

a = re.compile(r"\d+")

x = a.findall("hello12world34python56")

print(a)     #re.compile('\\d+')   #对象不变,属于不可变对象,方便后面反复使用

print(x)     #['12', '34', '56']


#或者使用re模块的函数

x = re.findall(r"\d+","hello12world34python56")    #需要指定正则表达式

print(x)

测试效率

start_time = time.time()

a = re.compile(r"\d+")

for i in range(1000000):

    x = a.findall("hello12world34python56")

    x = a.match("hello12world34python56")

end_time = time.time()

print(f"方法一用了{end_time-start_time}秒")


start_time = time.time()

for i in range(1000000):

    x = re.findall(r"\d+","hello12world34python56")

    x = re.match(r"\d+","hello12world34python56")

end_time = time.time()

print(f"方法二用了{end_time-start_time}秒")

结果:

 SHAPE  \* MERGEFORMAT

一般情况下,对于个人使用者,两种方式在效率上的区别不大,只不过Pattern正则对象方便反复调用。


二、r前缀和\

2.1反斜杠,\

【两个含义】:

  • 转义 用于转义特殊字符,让特殊字符的功能失效。
  • 特殊序列功能 有一些特定的序列功能是由\组成的。\在这里是一个组成单元。比如\d代表匹配任何十进制数字


?表示对它前面的正则式匹配01次重复,\?表示?,不再是功能符号。

【特殊序列功能】

\d表示匹配任何十进制数字;这等价于类[0-9] 。这里不是代表匹配字母"d"

如果是”\\d“因为转义的存在,先将”\\“表现为”\“,然后与”d“组合成匹配数字的功能。并不会匹配”\“。


2.2r前缀

这里先介绍,pycharm在正则表达式的提示功能。

 SHAPE  \* MERGEFORMAT

pycharm中,在输入字符串时。

  • 绿色代表”所见即所得“。
  • 橘色代表转义。
  • 黄色代表在正则表达式中的特殊功能。 在代表表达式的地方输入,就可能会出现黄色。

※※正则表达的多次转义:

在不使用r前缀的情况下,正则表达式会因为多次转义而导致,与我们的预期不符合。

比如:

我们想要匹配【"\"+"数字"】。

从图中,我们看一下。

 SHAPE  \* MERGEFORMAT

在不加r前缀的情况,\\\d中的\d功能已经失效。因为在正则表达式中,会多次转义。

实际上,在不使用r前缀的时候。四个反斜杠是单斜杠,八个反斜杠才是双斜杠,不正确的分斜杠个数可能就会破坏后面的序列功能。


※※r的功能:不再多次转义。但仍有转义过程。

这个时候,r"\\"代表单斜杠,r"\\\\"代表双斜杠。

r"\\\d"中的\d仍然可以匹配数字。"\\\d"就不能匹配数字了。


【提示】:如果不是很熟悉正则表达式,那么在pycharm上编写,可以通过颜色大致进行判断。

三、特殊字符

我们通过a = re.compile(".") 创建正则表达式Pattern对象。 #默认方式

也可以指定Pattern对象的flags属性。这个属性,会对匹配方式造成一些影响,这里做具体的讲解。

a = re.compile(".",re.DOTALL) #指定flags属性

正则匹配对象的创建方法。第二种方法,指定了一个


3.1 点号.

(点) 在默认模式,匹配除了换行的任意字符。

如果指定了flags属性re.DOTALL,它将匹配包括换行符的任意字符。指定方式。

a = re.compile(".",re.DOTALL)

标签属性可能比较难以记住,但是编辑器的代码提示功能会帮我们解决这个问题。平时多写写就习惯了。

匹配点号:r"\."

例子:①re.compile(".123")匹配"A123",②re.compile(".123")不匹配"A\n123",

re.compile(".123",re.DOTALL)匹配"A\n123"的”\n123"

re.compile(".*123",re.DOTALL)匹配"A\n123""A\n123"

flags其他属性这里先不用过于在意,后面再讲。

3.2 星号*

对它前面的正则式匹配0到任意次重复, 尽量多的匹配字符串。ab*会匹配'a''ab',或者'a'后面跟随任意个'b'

匹配星号:r"\*"

例子:ca*t 将匹配 'ct' (0 'a' 字符)'cat' (1 'a' ) 'caaat' (3 'a' 字符),等等。

3.3 问号?

对它前面的正则式匹配01次重复,尽量多的匹配。 ab? 会匹配 'a' 或者 'ab'

匹配问号:r"\?"

例子:ab? 会匹配 'a' 或者 'ab'

3.4 小括号()

小括号里面看成一个整体

匹配小括号:用 \( \)转义, 或者把它们包含在字符集合里: [(], [)].

例子:1(23)+匹配12312323.....

其他功能:括号的功能很多,比如分组。这个在后面再将。第七节。

3.5 中括号[]

[]用于表示一个字符集合。内部可以用-链接

匹配中括号:\[ \]转义

例子:[a-c]是一个集合,代表匹配集合里面的一个字符。 与后面3.9的竖线|对比着看。

[a-c][1-3]匹配a3c1,不匹配aaa5d6

※※※集合的特殊情况

  • 特殊字符在集合中,失去它的特殊含义。比如 [(+*)] 只会匹配这几个文法字符 ‘(, +, *, )
  • -在中括号外部就是"-"字符,在中括号内是一个连接符,[a-z]表示az的集合
  • ^的情况,具体看3.10小节。


3.6加号+

对它前面的正则式匹配1到任意次重复,尽量多的匹配。

匹配加号:\+

例子:ab+ 会匹配 'a' 后面跟随1个以上到任意个 'b',它不会匹配 'a'

\d+匹配多个数字组合,比如131131789

3.7大括号{}

用法1{m}

对其之前的正则式指定匹配 m 个重复;少于 m 的话就会导致匹配失败。

例子: a{6} 将匹配6 'a' , 但是不能是5个。

用法2{m,}

对其之前的正则式指定匹配至少m 个重复,尽量取多。少于 m 的话就会导致匹配失败。

例子: a{6} 将与 'aaaaaaaaaaaaaa' 匹配, 得到全部a

用法2{m,n}

对正则式进行 m n 次匹配,在 m n 之间取尽量多

例子:a{3,5} 匹配 'aaaaaa'得到5a

用法3{m,n}?

非贪婪模式,只匹配尽量少的字符次数。

例子:a{5,10}?10a匹配,将匹配成功两次,都是五个a


补充:贪婪和非贪婪

区分贪婪和非贪婪,是为了更好的获得我们想要的内容。并且不与正则表达式的其他本意冲突。

*', '+',和 '?' {m,} {m,n}修饰符都是贪婪的。就算?问号只匹配0个或者1个,也是贪婪的。

例子1如果我们要从内容"head内容"找出标签

<.*>或者<.+>或者<.{1,}>或者<.{1,99}>将匹配整个字符串:head内容.

这与我们的预期不一致。我们想要的是这种形式。

贪婪的意思是找出符合条件的{BANNED}最佳长字符串

我们使用非贪婪方式。

*? +? ? ? {m,} ? {m,n}? 是非贪婪字符。就是在贪婪的功能后面加上一个问号。

<.*?>或者<.+?>或者<.{1,}?>或者<.{1,99}?>将只能匹配出来,多次匹配后,分别也会将后面的内容匹配出来,当然这需要与相应的函数配合。


例子2:上面讲了 *、+ {m,} {m,n}的贪婪影响的例子。还没有讲?。这个例子就讲?问号的情况。

一般情况?问号因为{BANNED}最佳多匹配一次,所以很难对其他内容造成影响。但是在分组的时候,还是会造成影响。分组的具体含义,后面再讲,这里先看看案例。

匹配电话号码:下面的例子看不懂可以不同管,主要是介绍?对于匹配的影响。

a = re.compile(r'(.?)(\d+-\d+-\d+)')

x = a.match("131-1111-1111")

print(x)

print(x.group(2))

 SHAPE  \* MERGEFORMAT

虽然成功匹配了电话号码131-1111-1111,用到分组x.group(2)。然后发现保存的内容出错了。

我们改成非贪婪模式。(.?)改成(.??)

关于分组,这里不用过于纠结。后面再具体讲。



3.8美元符 $ 行尾符

默认:匹配字符串尾或者在字符串尾的换行符的前一个字符。

他是行尾限制符。

com$代表以com结尾。

.*com$如果与"\"匹配,只能匹配到''

因为,$虽然是行尾符,但是在默认匹配模式下面,是不能跨行限制的

如果添加re.MULTILINE属性。那么可以根据换行符分割尾部。

print(re.findall(".*com$","\",re.MULTILINE))得到

['', '']两个匹配结果。

补充:

$ 行尾

\Z 字符串尾 无论是否设置re.MULTILINE属性,它都指的字符串的尾巴用什么结尾。

print(re.findall(".*com\Z","\",re.MULTILINE))得到

['']


3.9 竖线符 |

A|B, A B 可以是任意正则表达式匹配 A 或者 B '|' 分隔开的正则样式从左到右进行匹配。当一个样式完全匹配时,这个分支就被接受。意思就是,一旦 A 匹配成功, B 就不再进行匹配,即便它能产生一个更好的匹配。或者说,'|' 操作符绝不贪婪。

例子:

aa|bb|cc与aabbcc匹配,将得到aa。使用findall函数,会得到aabbcc。因为匹配了三次。先看前面两个字符,成功了。继续从第三个,判断,34个字符匹配仍然成功。一直到第三次匹配成功。

aa|aabb|aabbcc与aabbcc匹配,只能得到aa。因为从左到右绝不贪婪。并不会为了让结果匹配的更多,而变成aabbcc

看一下与中括号的区别:

[abc]与aabbcc匹配,可以成功匹配6次。

print(re.findall('[abc]',"aabbcc"))结果是['a', 'a', 'b', 'b', 'c', 'c']


3.10 插入符号 ^ 行头符

作用1:

匹配字符串的开头,这个跟$类似,默认没有打开跨行限制,根据情况设置re.MULTILINE

^www.*与"\"匹配,得到

设置re.MULTILINE后,可以匹配 

补充:

^ 行头

\A 字符串头 无论是否设置re.MULTILINE属性,它都指的字符串的头部用什么开头。

print(re.findall("\Awww.*","\",re.MULTILINE))得到

['']


作用2:

[]中括号内首字母是^,代表取反。

[^1-47-9]+与"0124558952abc334\n876"匹配,结果['0', '55', '5', 'abc', '\n', '6']

[^1-47-9]也会包含进去换行符


四、序列功能

另外,序列功能的大小写,一般对应相反关系,有些没有对应的相反关系。下面做一个总结。

4.1 \A 只匹配字符串开始

前面提到了\A ^都是用什么开头,\A是字符串开头,限制更加严格,^是行开头。

print(re.findall("^1.*","132541256\n1245",re.MULTILINE))

结果:['132541256', '1245'] ^是行开头,设置re.MULTILINE支持多行。^1.*匹配1开头的行。


print(re.findall("\A1.*","132541256\n1245",re.MULTILINE))

结果:['132541256'] \A是字符串开头,设置re.MULTILINE也没用。^1.*开头的字符串

4.2\b \W \w

\w 匹配字母数字下划线,

\W 匹配字母数字下划线

\w \W一起组成Unicode字符。但是上面的介绍一般是对于英文使用者。

案例:

import re

str_="今天,温度是26℃ 明天,是28℃。"

print(re.findall("\b+",str_))

print(re.findall("\w+",str_))

print(re.findall("\W+",str_))

结果:

 SHAPE  \* MERGEFORMAT

也就是。\w匹配字母数字下划线,虽然这么说,但是实际上,中文也在这个范围。

对于不同的语言环境,必要的测试是必要的。

\W匹配的是各种符号。空格也在这个范围。


\b 匹配空字符串“”,匹配的单词边界。不输出实际的空字符串。

\B 匹配空字符串,但  能在词的开头或者结尾。


\B和\b的用法和例子可以看看下面的例子。

正则表达式里\b和\B,Python实例_uvyoaa的专栏-CSDN博客_python正则表达式\b



4.3\d \D

\d 匹配任意数字,等价于 [0-9]

\D 匹配任意非数字 在中文环境是匹配了中文的。

\d 与"123一二三"匹配,可以匹配到123


4.4\s \S

\s匹配任意空白字符,等价于[\t\n\r\f\v]

\S匹配非\s

print(re.findall("\s+","今天  \n,明天\f后天"))

print(re.findall("\S+","今天  \n,明天\f后天"))

 SHAPE  \* MERGEFORMAT

4.5\Z

前面提到了,代表字符串尾。不再举例。

五、flags属性

之前稍微提了一下,正则对象,可以设置属性。属性只读。

a = re.compile(".+",re.A)

print(a)          #re.compile('.+', re.ASCII)


a = re.compile(".+")

a.flags = re.A    #报错:AttributeError: readonly attribute

print(a)

这个也用于re的函数中。

a = re.compile(".+",re.DOTALL)

print(a.findall("今天天气好\n明天天气不好"))


##等价

print(re.findall(".+","今天天气好\n明天天气不好",re.DOTALL))

5.1re.A re.ASCII

两种方法书写都是可以的。

 \w, \W, \b, \B, \d, \D, \s \S 只匹配ASCII,而不是Unicode。这只对Unicode样式有效,会被byte样式忽略。

上面的解释是官网文档上的解释。但是我们作为中文使用者,实际上会遇到一些坑。

案例:

a = re.compile("\S+好",re.A)      

print(a.findall("今天天气好\n"))

a = re.compile("\S+好")           

print(a.findall("今天天气好\n"))


a = re.compile("\w+好",re.A)

print(a.findall("今天天气好\n"))

a = re.compile("\w+好")

print(a.findall("今天天气好\n"))

 SHAPE  \* MERGEFORMAT

\S应该匹配非[\t\n\r\f\v]的其他字符

我们发现,\S不论是否设置re.A,都仍然匹配了中文。

\w设置re.A后,就不会匹配中文了。但是官方文档写的【 \w, \W, \b, \B, \d, \D, \s \S 只匹配ASCII】。

所以,设置ASCII 限制的时候,{BANNED}最佳好测试一下,一般\w不会出错。

如果我们需要设置,提取所有字符,就用.*

提取ASCII 字符,就用\w*,并设置re.ASCII


5.2 re.DEBUG 无缩写

显示编译时的debug信息。无论成功不成功,都会显示一串信息。

a = re.compile("\w+",re.DEBUG)

print(a.findall("今天天气好\n"))   


a = re.compile("[0-9a-zA-Z]+",re.DEBUG)

print(a.findall("今天天气好\n"))

 SHAPE  \* MERGEFORMAT


5.3 re.I re.IGNORECASE 进行忽略大小写匹配

也可以用在[a-z]这种地方。

print(re.findall("[a-z]+", "天天up"))     #['up']

print(re.findall("[a-z]+", "天天UP"))     #[]

print(re.findall("[a-z]+", "天天UP", re.IGNORECASE))       #['UP']


print(re.findall("ONE", "one,two,three"))                  #[]

print(re.findall("ONE", "one,two,three", re.IGNORECASE))   #['one']


5.4 re.L re.LOCALE

由当前语言区域决定\w,\W,\b,\B和大小写敏感匹配。这个标记只能对byte样式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一个 "习惯”,而且只对8位字节有效。Unicode匹配在Python 3 里默认启用,并可以处理不同语言。


5.5 re.M re.MULTILINE 跨行操作

前面讲^$的时候提到过,他们的操作针对的行头和行尾,但是如果不设置跨行,就只会对{BANNED}中国第一行的行头匹配,或者{BANNED}最佳后一行的行尾匹配。

如果忘记了。可以翻上去看看,这里不写了。

5.6 re.S re.DOTALL 点号匹配换行符

.号的时候,提到过.不包含换行符,如果需要换行符弄进来,需设置re.DOTALL


5.7 re.X re.VERBOSE

添加注释。用不用看个人。

下面是标准写法。

a = re.compile(r"今天.*明天.*后天.*")

str_ = "今天吃鸡,明天吃鸭子,后天吃鹅"   

print(a.findall(str_))    #['今天吃鸡,明天吃鸭子,后天吃鹅']

下面是修改后。

a = re.compile(r"""今天.*    # 注释1

                  明天.*    # 注释2

                  后天.*   # 注释3                  

                   """,re.VERBOSE)   

str_ = "今天吃鸡,明天吃鸭子,后天吃鹅"   

              

print(a.findall(str_))    #['今天吃鸡,明天吃鸭子,后天吃鹅']


六、正则表达的函数、匹配对象re.Match object

正则表达的函数和正则对象的方法是一一对应的。之前也提到过想用哪种书写方式就用哪种书写。

6.1 .search(string[, pos[, endpos]])

找到匹配样式的{BANNED}中国第一个位置,并返回一个相应的匹配对象。如果没有匹配,就返回一个None; 注意这和找到一个零长度匹配是不同的。

它的返回值是一个对象。re.Match object。比如

Match object的方法:

  • .span():匹配的位置 span()---->(1, 4)
  • .match():匹配的内容 match()---->'123'
  • .start() 匹配结果的开始位置 start() ---->1
  • .end() 匹配结果的结束位置 end() ---->4

案例:

a = re.compile("\d{3}")    #匹配三个数字

x = a.search("a123b1234c567")

print(x)

<re.Match object; span=(1, 4), match='123'>     #因为是找到search是找{BANNED}中国第一个匹配项目,所以只有一个结果。

根据属性,我们可以通过span()获得位置(1, 4),通过match()获得'123'

比如菜鸟教程上的案例。

另外,我们使用search()的时候必须判断是否为None

print(re.match('com', '').span())

.

补充,因为还有.start() .end() 方法。所以我们可以获取匹配结果以外的值。

a = re.compile("\d{3}")    #匹配三个数字

str_ = "a123b1234c567"

x = a.search(str_)

print(str_[:x.start()]+str_[x.end():])    #ab1234c567 获取匹配结果以外的值


6.2 .match(string[,pos[,endpos]])

尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话match() 就返回 none

.match()与.search()的区别就是是不是从字符串开始。

a = re.compile("\w\d{3}")

str_ = "a123b1234c567"

x = a.match(str_)

print(x)     #

可能之前在菜鸟教程上看到过.group()方法,而且结果还不一致。

这里就补充一下分组的概念。

这里就需要用到分组和Match object的另一个方法.group()

方法:

  • .group(num=0):某个组的匹配结果,默认组0,组0就是全部匹配结果。
  • groups():所有组的匹配内容

案例:

a = re.compile("\w\d{3}中间字符.*")

str_ = "a123中间字符b1234c567"

x = a.match(str_)

print(x)

print(x.group())      #print(x.group(0))等价,就是输出结果的意思。

print(x.group(0))     #上面一个意思

print(x.group(1))     #报错,因为没有{BANNED}中国第一组

print(x.group(2))     #报错,因为没有第二组

print(x.group(3))     #报错,因为没有第三组

先简单说明一下,括号里面的内容不仅看成一个整体,还可以看成一个分组。

这里修改。\w\d{3}中间字符.*修改为(\w\d{3})中间字符(.*) 这里不会改变匹配结果,但是会影响函数的输出。

其中,(\w\d{3})就是{BANNED}中国第一组,(.*)是第二组。

所以:

a = re.compile("(\w\d{3})中间字符(.*)")

str_ = "a123中间字符b1234c567"


x = a.match(str_)

print(x)                 #

print(x.group())         #a123中间字符b1234c567

print(x.group(0))        #a123中间字符b1234c567

print(x.group(1))        #a123   #{BANNED}中国第一个分组匹配的是a123

print(x.group(2))        #b1234c567     #第二个分组匹配的是b1234c567

print(x.group(3))        #报错,没有第三组。

groups()就是返回所有组的信息。比如上面的例子就是返回:('a123', 'b1234c567')

七、小括号及分组

前面提到了将括号中的字符作为一个分组。

7.1内联标记

前面提到了re.IGNORECASE模式,忽略大小写。但是只想在某个地方使用忽略,而不在其他地方忽略呢?就可以用到内联标记。

re.IGNORECASE (?i:)

用法:(?i:正则表达式)

a = re.compile("(super) APP")

x = a.search("SUPER APP")

print(x)                #不能用super匹配SUPER


a = re.compile("(?i:super) APP")

x = a.search("SUPER APP")

print(x)       #     #可以用super匹配SUPER

看到这里,你应该已经理解了。内联标记实际就是匹配模式的部分范围生效。

re.MULTILINE (?m:) '^''$'的跨行限制开头结尾。

re.DOTALL (?s:) '.'支持换行符的匹配。

re.VERBOSE (?x:) 支持注释

re.ASCII (?a:) \w, \W, \b, \B, \d, \D, \s \S 只匹配ASCII



补充:

多种标记方式:(?im: ) im可以换成任何你想要加的属性,只要不冲突就行。

比如:(?is:)

a = re.compile(r'(?i:天天.*up)')

str_ = '天天UP天天\nUP'

x = re.match(a,str_)

print(x)       #     #点号不能匹配换行符


a = re.compile(r'(?is:天天.*up)')

str_ = '天天UP天天\nUP'

x = re.match(a,str_)

print(x)     #    #支持忽略大小写,并让点号匹配换行符


排除标记方式:

比如flasg属性中,已经指定了忽略大小写,但是在某个地方一定不能忽略大小写。那么我们就可以设置排除标记方式。

(?-im: )im可以换成任何你想要加的属性。?后面的-就是排除flasg属性的意思。

例子:例子不好举。这里就用pythonNone举例。

None expressing empty。None 不能忽略大小写,而后面的可以忽略大小写。

a = re.compile(r'(?-i:None).*empty',re.IGNORECASE)

str_1 = 'none expressing EMPTY'

str_2 = 'None expressing EMPTY'

x = a.match(str_1)

print(x)     #None   因为str_1 none 不能忽略大小写,所以无匹配项。


x = a.match(str_2)

print(x)     #     #分组内不忽略大小写,其他地方忽略。

另外,还支持这种方式:(?ms-i: )


7.2分组

() 按照顺序,从1开始分组。

之前提到了()小括号就是一个分组。

比如,座机号码有区号和号码。{BANNED}中国第一组区号,是三到四个数字,第二组号码(5-8位),首位非0,其他的是数字

(\d{3,4})-([1-9]\d{4,7}){BANNED}中国第一组是区号,第二组是号码。


\number 它不是创建分组功能,是调用分组的功能。

常用在匹配html标签。

html匹配的时候,我们一般找的都是标签对应的部分。

html与/html对应,所以html设置一个分组,/html可以用\number代替。

a = r'<(.+)><(.+)>.+'

str_ = 'head部分'

x = re.match(a,str_ )

print(x)                  #

print(x.groups())         #('html', 'head')


(?p)创建组 (?p=name)引用组

上面的例子,可以用这种形式修改,组的名字。称为命名组。

a = r'<(?P.+)><(?P.+)>.+'

str_ = 'head部分'

x = re.match(a,str_)

print(x)                        #

print(x.group('label_1'))       #head

print(x.group(1))               #html

print(x.group('label_2'))       #html

print(x.group(2))               #head

print(x.groups())         #('html', 'head')

我在上面的例子中,特意将{BANNED}中国第一个分组,命名为label_2,第二个分组命名为label_1

我们看到x.group('label_1')输出的就是第二个分组,x.group(数字)是按照分组序号输出。

x.groups()是按照123这种序号顺序输出的。

好处:在不同的网页中,可能都存在我们想要的信息,但是位置不同,我们可以通过分组命名方式,然后用同一个名字输出想要的内容。而不用在意他们的顺序。


7.3扩展标记法

①非捕获组 (?:)

它的意思就是让小括号只具备看成一个整体的意义,而不具备分组意义。

例子:

x = re.search('([a-z][A-Z])+\d','aa12aA1')

print(x)                       #

print(x.group(1))              #aA

普通方法,x.group(1)能输出组的匹配内容。

但是:

x = re.search('(?:[a-z][A-Z])+\d','aa12aA1')    #(?:),让小括号不具备分组的意义

print(x)             #

print(x.group(1))    #报错 IndexError: no such group

我们看到匹配的整体结果是不变的,但是x.group(1)已经不能输出组的内容,因为组已经不存在了。


(?#) 注释,里面的内容会被忽略。

这个就是字面意思,一看就懂。


③ 断定标记

有四个:

(?<=)xx去匹配xx,但是前面的内容也必须满足条件。

(?<!)xx去匹配xx,但是前面的内容必须不能满足条件,就是取反。

xx(?=)去匹配xx,但是后面的内容也必须满足条件。

xx(?!) 去匹配xx,但是后面的内容也必须不能满足条件,就是取反。

共同特点,断定标记里面的内容用于额外判断,但不作为匹配结果。

比如,你要检索一串数字,但是需要前后的字符串满足条件,而且输出结果只包含数字。

a = re.compile('\d+(?=good)')

print(a.search("a12B3H5good56"))


a = re.compile('\d+(?!good)')

print(a.search("1good12a"))



a = re.compile('(?<=good)\d+')

print(a.search("a12good3H5Z56Z"))



a = re.compile('(?

print(a.search("a12z56good3H5Z56"))

结果:

 SHAPE  \* MERGEFORMAT

④判断匹配。(?(id/name)yes-pattern|no-pattern)

这个功能很强大,可以理解成正则表达式的条件判断。

某个组,如果匹配成功,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-patternno-pattern 可选,也可以被忽略。

 SHAPE  \* MERGEFORMAT

官方文档上,只给了尝试匹配的效果

下面补充一个例子。

比如,匹配qq邮箱和126邮箱,我记得当初我注册126邮箱的时候,要求{BANNED}中国第一个必须是字母。不管,现在变化了没有,但是我们仍然这样规定。

qq邮箱:@前面全部是数字。

126邮箱:@前面{BANNED}中国第一个是字母,其他的是字母、数字、下划线。

a = re.compile(r'^((\d+)|[a-zA-Z](?a:\w+))@(?(2)(qq)|(126))\.com$')


print(a.search('12345@qq.com'))

print(a.search("a123@qq.com"))

print(a.search("123@qq.com"))

print(a.search("a123@126.com"))

print(a.search("a1中文23@126.com"))

print(a.search("a123_15@126.com"))

print(a.search("123@126.com"))

 SHAPE  \* MERGEFORMAT

 SHAPE  \* MERGEFORMAT


八、其他函数

8.1 .fullmatch(string[, pos[, endpos]]) 全部匹配

前面提到过,matchsearch的区别是,match需要是起始位置匹配成功。

fullmatch需要是起始位置和结束位置同时匹配成功,也就是整个字符串匹配成功。

实际上searchmatchfullmatch是可以相互转化成等价意义的。

re.search("小明", "张小明说他累了")    #只有它能匹配

re.match("小明", "张小明说他累了")

re.fullmatch("小明", "张小明说他累了")

我们加上限制符号

re.search("^小明", "张小明说他累了")re.match("小明", "张小明说他累了")一个意思。

re.search("^小明$", "张小明说他累了")re.fullmatch("小明", "张小明说他累了")一个意思。

print(re.match("小明", "张小明说他累了"[1:])) 加了字符串的切片,也能匹配成功。

re.fullmatch("小明", "张小明说他累了"[1:3]) 加了字符串的切片,也能匹配成功。


8.2 .split(string, maxsplit=0) 根据匹配分割 受到分组影响。

这里小括号有重要的影响。组里的文字也会包含在列表里。

例子:"1+2-3*5/6"进行分割,按照+-*/进行分割

re.split('[+\-*/]', "1+2-3*5/6")

结果是:['1', '2', '3', '5', '6']

之前再{BANNED}中国第一篇小结:数字型中提到过,如果我们要保留运算,使用Decimal数字类型。

那么我们就需要在分割的时候保留+-*/。我们使用分组。就是加一个括号就行了。

re.split('([+\-*/])', "1+2-3*5/6")

结果是:['1', '+', '2', '-', '3', '*', '5', '/', '6']

maxsplit=0 是指定分割的次数。默认0是不受限制的意思。

re.split('([+\-*/])', "1+2-3*5/6",maxsplit=2)

结果是:['1', '+', '2', '-', '3*5/6']


8.3 Pattern.findall(string[, pos[, endpos]]) 查询所有匹配,结果受到分组影响

返回字符串中的所有非重叠模式,作为字符串或元组列表。左右扫描字符串,匹配在找到的顺序中返回。结果包含空匹配

结果取决于模式中捕获组的数量。它的结果不一定是列表。

1.   如果没有分组,则返回与整个模式匹配的字符串列表

2.   如果恰好有一个组,请返回匹配该组的字符串列表

3.   如果存在多个组,则返回与组匹配的字符串的元组列表

4.   非捕获组不会影响结果的形式

正因为这样多变的特性,所以我们在没有理解小括号的全部意义的时候,findall的返回结果会让我们莫名其妙。

{BANNED}中国第一点:

例子1查找a1b22c333d4444e5555,中所有【一个字母+2个及以上数字】的匹配结果。

re.findall('[a-zA-Z]\d{2,}','a1b22c333d4444e5555')

结果:['b22', 'c333', 'd4444', 'e5555']

这是我们{BANNED}最佳常见的用法。比较简单。

第二点:

例子2这里我们对【一个字母+2个及以上数字】的【2个及以上数字】进行分组。加上括号。

re.findall('[a-zA-Z](\d{2,})','a1b22c333d4444e5555')

结果:['22', '333', '4444', '5555']

结果已经变了。这个在理解了分组之后,也很好理解。使用了分组之后,findall不会输出分组以外的内容。


例子3:如果我们又要使用分组功能,又想要输出全部内容,那么就在整体外面再加一个括号。

re.findall('([a-zA-Z](\d{2,}))','a1b22c333d4444e5555')

结果是:[('b22', '22'), ('c333', '333'), ('d4444', '4444'), ('e5555', '5555')]

看到元组列表中,每个元组的{BANNED}中国第一项就是全部内容的结果。

第三点:如果存在多个组,则返回与组匹配的字符串的元组列表。。上面例子3就是也可以当做案例。

第四点:我们讲例子2中的小括号弄成非捕获组。

re.findall('[a-zA-Z](?:\d{2,})','a1b22c333d4444e5555')

结果:['b22', 'c333', 'd4444', 'e5555']   #原来的结果是['22', '333', '4444', '5555']


8.4 .finditer(string[, pos[, endpos]]) 查询所有匹配,生成迭代器

findall的区别

finditer返回的是迭代器,需要再next()后使用, 或者在循环中使用。

②如果有分组的存在。

findall会输出分组里面的内容,而不输出分组以外的匹配内容。

finditer会迭代输出每一个匹配结果的re.Match对象,所以finditer的信息会更加完整。

举个例子:

x=re.findall('(\d+)-(\d+)-(\d+)','131-1111-1111,132-2222-2222')

print('findall的结果,只有分组信息:',x)

findall在分组的时候,只能输出分组信息。

 SHAPE  \* MERGEFORMAT

x = re.finditer('(\d+)-(\d+)-(\d+)', '131-1111-1111,132-2222-2222')

num = 1

for i in x:

    print(f'finditer第{num}个匹配结果:', i)    #可以通过i.match()获得匹配的全部而内容,而不是只有分组内容。

    print(f'finditer第{num}个匹配结果的分组信息:', i.groups())

    num += 1

 SHAPE  \* MERGEFORMAT

总结:

如果你只需要分组的信息,那么可以用findall

如果你还想使用分组以外的内容,那么可以用finditer


8.5 .sub(repl, string, count=0) 检索和替换

首先明确:替换是全部替换还是替换分组?

我们测试一下:

x = re.sub('(\d+)-(\d+)-(\d+)','替换内容', '131-1111-1111,132-2222-2222')

print(x)

 SHAPE  \* MERGEFORMAT

这里是把整个匹配结果替换了。

实际上repl参数是指的替换成的内容。可以是字符串,也可使函数。

如果是字符串,那么就直接是匹配结果全部替换。

如果是函数,就可以有目的的选替换内容。

比如上面,131-1111-1111我想把中间的号码改成****变成131-****-1111

def sub_func(match):

    return match.group(1)+"****"+match.group(3)   #将【组1+"****"+2】返回



x = re.sub('(\d+)-(\d+)-(\d+)', sub_func, '131-1111-1111,132-2222-2222')

print(x)

这样就实现了匹配内容的部分替代。

我们再看看分组在这里的重命名。

我们分别把(\d+)-(\d+)-(\d+)的三个组的名字命名为group1group2group3

def sub_func(match):

    return match.group('group1')+"****"+match.group('group3')



x = re.sub('(?P\d+)-(?P\d+)-(?P\d+)', sub_func, '131-1111-1111,132-2222-2222')

print(x)

没有问题。所以sub里面(?P)的分组不受影响。

之前在分组里面讲了,在正则表达式里面可以通过\n序号引用分组; 通过(?p=name)组名引用分组。

sub函数里面,有一个独特的repl参数引用分组功能。比如\g \g<8>这种形式

改写上面的案例。

x = re.sub('(?P\d+)-(?P\d+)-(?P\d+)',

           '\g****\g', '131-1111-1111,132-2222-2222')

print(x)

 SHAPE  \* MERGEFORMAT

是不是很方便、很强大?


8.6 .subn(repl, string, count=0) 检索和替换、以及替换次数

功能与.sub(repl, string, count=0)几乎一模一样。比如调用8.5中的{BANNED}最佳后一个案例,只把sub改成subn

x = re.sub('(?P\d+)-(?P\d+)-(?P\d+)',

           '\g****\g', '131-1111-1111,132-2222-2222')

print(x)

改成subn

x = re.subn('(?P\d+)-(?P\d+)-(?P\d+)',

           '\g****\g', '131-1111-1111,132-2222-2222')

print(x)

 SHAPE  \* MERGEFORMAT

sub的结果是字符串(替换后的内容),subn的结果除了一个字符串(替换后的内容),还有替换的次数。返回一个元组(字符串,替换次数)


九、正则表达式Pattern对象的属性。

前面提到过,Patternflags属性。因为re模块的函数,也支持传入flasgs信息。

Pattern.subn(repl, string, count=0)

re.subn(pattern, repl, string, count=0, flags=0)

所以两种写法没啥区别。

但是下面介绍两个re模块函数不能使用的参数,而Pattern具备的属性。主要用于测试。

Pattern.groups 分组的数量

import re

a = re.compile('(?P\d+)-(?P\d+)-(?P\d+)')

print(a.groups)   # 3     #一共三个组


a = re.compile('(\d+)-(\d+)-(\d+)')

print(a.groups)   # 3     #一共三个组


a = re.compile('\d+-\d+-\d+')

print(a.groups)  # 0     #没有分组


Pattern.groupindex 映射由(?P)定义的命名符号组合和数字组合的字典。如果没有符号组,那字典就是空的。

import re

a = re.compile('(?P\d+)-(?P\d+)-(?P\d+)')

print(a.groupindex)    #只有这种形式可以返回。


a = re.compile('(\d+)-(\d+)-(\d+)')

print(a.groupindex)       # 序列组不能返回


a = re.compile('\d+-\d+-\d+')

print(a.groupindex)       # 这里没有分组

上面两个属性,一般都是用于测试的,查看分组情况。当然也可以用于循环,分别输出分组的信息。避免分组不存在而报错。


Pattern.pattern 编译对象的原始样式字符串

import re

a = re.compile('(?P\d+)-(?P\d+)-(?P\d+)')

print(a.pattern)

print(a)

请查看区别。

十、匹配返回对象re.Match的方法

前面提到过。

.span() 返回匹配对象的切片位置。比如(1,3)

.start([group]) 返回结果的,开始位置。如果不指定group,就是group(0)也就是整个匹配字符串。

.end([group]) 返回结果的,结束位置。如果不指定group,就是group(0)也就是整个匹配字符串。

.match() 返回匹配对象的对应的字符串。

.group(数字或者分组名) 返回匹配对象的某一个分组的对应的字符串。

.groups() 返回匹配对象的所有分组的对应的字符串,则称一个字符串元组。

除了.findall()方法,其他的方法都可以直接或者间接得到re.Match对象。

finditer()得到re.Match对象组成的迭代器。

sub(){BANNED}最佳后的结果虽然是字符串。但是,我们可以在替换步骤,可以通过替换函数调用re.Match对象。

所以re.Match对象是正则表达式不得不提的内容。

这里对re.Match对象的内容做一个补充。

10.1 Match.groupdict(default=None) 命名组的字典信息。

可以将分组的信息组成一个字典。只能是命名组,不能是序号组。

a = re.match(r"(?P\w+) (?P\w+)", "Malcolm Reynolds")

a.groupdict()      #{'first_name': 'Malcolm', 'last_name': 'Reynolds'}


a = re.match(r"(?P\w+) (\w+)", "Malcolm Reynolds")

a.groupdict()      #{'first_name': 'Malcolm'}   #因为第二个组是序号组,不是命名组,所以不能加入字典。


10.2 Match.expand(template) 匹配结果替换

听到替换,就想到了re.sub()实际上也是这个方法。

比如之前提到的sub()的例子。

x = re.sub('(?P\d+)-(?P\d+)-(?P\d+)',

           '\g****\g', '131-1111-1111,132-2222-2222')

print(x)

我们这里改成finditer

x = re.finditer('(?P\d+)-(?P\d+)-(?P\d+)', '131-1111-1111,132-2222-2222')

for i in x:

    print(i.expand('\g****\g'))

是不是效果差不多?所以我有理由相信,sub()实际上就是finditer()Match.expand(template) 结合的产物。

Pattern.finditer()是检索,Match.expand()是替换Pattern.sub()是检索和替换。

这样我们就从底层了解了各种函数的功能和作用。


10.3 Match.__getitem__(g) 分组引用

之前提到过,分组的引用一般都是通过Match.group(数字或者命名组的名字)

python3.5之后,支持,通过__getitem__获取组。

也就是Match[1]或者Match["name"]这种形式。

原文:

阅读(30990) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~