凯发app官方网站-凯发k8官网下载客户端中心 | | 凯发app官方网站-凯发k8官网下载客户端中心
  • 博客访问: 1619817
  • 博文数量: 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正则表达式详解-凯发app官方网站

正则表达式的用法较多,也比较灵活。网上的资料不是很全面。自己之前也只学了一点点,这里复习一下,并按照官方文档做一个总结,按照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的用法和例子可以看看下面的例子。



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样式忽略。

上面的解释是凯发k8官网下载客户端中心官网文档上的解释。但是我们作为中文使用者,实际上会遇到一些坑。

案例:

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"]这种形式。

原文:

阅读(2785) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
")); function link(t){ var href= $(t).attr('href'); href ="?url=" encodeuricomponent(location.href); $(t).attr('href',href); //setcookie("returnouturl", location.href, 60, "/"); }
网站地图