正则表达式之特殊字符辨析(上)

本文提要:本文将系统讲解正则表达式中的前8组特殊字符(元字符)。


学习目标

学习完本系列文章后,你将能够看懂这样的正则表达式规则:
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
并基于此自主实现一个使用正则表达式验证邮箱合法性的python小程序,相信这将成为你学习正则表达式以至于python上的一个小小的里程碑。


正则表达式

正则表达式(Regular Expression),是对字符串操作的一种“逻辑公式”,它是由一些特定的字符组成的一个“匹配规则”,可以对要匹配的字符串指定“规则”,匹配总是从左到右进行的。

  • 正则表达式本质上是字符串,不可随意增减空格

  • 正则表达式中的反斜杠(就是键盘上Enter键上面的那个斜杠)用于特殊字符的转义

    正则表达式中使用反斜杠表示转义,所以当你想要写一个反斜杠的时候,你需要使用两个反斜杠,而python中也使用反斜杠表示转义,想要表示一个反斜杠的时候也需要写两个反斜杠,所以我们用python写正则表达式,如果需要表示一个反斜杠,那么需要4个反斜杠。

  • 为了防止python的转义加正则的转义把我们绕晕,我们最好使用原始字符串(在字符串前面加一个英文小写字母r),这样只需要考虑正则表达式的要求。

我们重点学会下面的特殊字符的表示:
. ^ $ * + ? { } [ ] \ | ( )
我们使用如下的代码结构来验证编写的规则:

import re #  re 模块只需要导入一次
p = re.compile(r'')
print(p.search("")) 
print(p.findall(""))

上述p<class 're.Pattern'>,拥有search()findall()两个方法:

  • search()方法扫描作用的字符串后,若扫描过程中找到匹配立即返回一个<class 're.Match'>,若不含匹配则返回None
  • findall()方法扫描整个字符串后,若含有匹配返回全部匹配项的列表,若不含匹配返回一个空列表[]

1、配位元字符 —— .

p = re.compile(r'.') 
print(p.search("abc\n")) 
# <re.Match object; span=(0, 1), match='a'>
print(p.findall("abc")) # ['a', 'b', 'c']

span表示跨度,即(pos,endpos),匹配项从posendpos,左闭右开:包括pos而不包括endpos,后面的例子中在不引起歧义的情况下将不再给出,直接给出匹配的结果。

p = re.compile(r'.', flags=re.S) 
# p = re.compile(r'.', flags=re.DOTALL)是等价的表达
print(p.search("\nbc")) # '\n'
print(p.findall("\nbc")) # ['\n', 'b', 'c']

flags=re.Sflags=re.DOTALL是旗标,用于增加re.compile()的效果,且这些旗标也是定义在re模块中的。

总结:.元字符的用法是匹配任意一个除换行符外的字符,当使用re.S或re.DOTALL旗标时,则可以匹配任意一个字符包括换行符。

2、配头元字符 —— ^

p = re.compile(r'^ab')
print(p.search("abcd\nabfg")) # 'ab'
print(p.findall("abcd\nefg")) # ['ab']
p = re.compile(r'^ab', flags=re.M) 
# p = re.compile(r'^ab', flags=re.MULTILINE) 是等价的表达
print(p.search("abcd\nabfg")) # 'ab'
print(p.findall("abcd\nabfg")) # ['ab', 'ab']

总结:^元字符的用法是匹配字符串的开头,当使用re.M或re.MULTILINE旗标时,则可以匹配换行符之后的字符串的开头。

3、配尾元字符 —— $

p = re.compile(r'fg$')
print(p.search("abcdefg\n")) # 'fg'
print(p.findall("abcdefg\n")) # ['fg']
p = re.compile(r'd$', flags=re.M)
print(p.search("abcd\nefgd\n")) # 'd'
print(p.findall("abcd\nefgd\n")) # ['d', 'd']

总结:$元字符的用法是匹配字符串的末尾或字符串末尾换行符前的末尾,当使用re.M或re.MULTILINE旗标时,则可以匹配任意位置换行符前的末尾。

4、重复三剑客 —— * + ?

p = re.compile(r'ab*') 
print(p.search("abc abbb abbbba")) # 'ab'
print(p.findall("abc abbb abbbba")) 
# ['ab', 'abbb', 'abbbb', 'a']
p = re.compile(r'ab+') 
print(p.search("abc abbb abbbba")) # 'ab'
print(p.findall("abc abbb abbbba")) 
# ['ab', 'abbb', 'abbbb']
p = re.compile(r'ab?') 
print(p.search("abc abbb abbbba")) # 'ab'
print(p.findall("abc abbb abbbba")) 
# ['ab', 'ab', 'ab', 'a']

总结:*元字符的用法是匹配*前面一个字符的零到无穷次,+元字符的用法是匹配+前面一个字符的1到无穷次,?元字符的用法是匹配?前面一个字符的0个到1次。

5、重复三剑客 —— * + ?但非贪婪

p = re.compile(r'ab*?') 
print(p.search("abc abbb abbbba")) # 'a'
print(p.findall("abc abbb abbbba")) 
# ['a', 'a', 'a', 'a']
p = re.compile(r'ab+?') 
print(p.search("abc abbb abbbba")) # 'ab'
print(p.findall("abc abbb abbbba")) 
# ['ab', 'ab', 'ab']
p = re.compile(r'ab??') 
print(p.search("abc abbb abbbba")) # 'a'
print(p.findall("abc abbb abbbba")) 
# ['a', 'a', 'a', 'a']

总结:当在* + ?后面加上一个?时,匹配表现为非贪婪,即*变为匹配前面一个字符的零次,+变为匹配前面一个字符的一次,?变为匹配前面一个字符的零次。

6、次数限定符 —— {}

p = re.compile(r'ha{2}')
print(p.search("bilibilihaahahaa")) # 'haa'
print(p.findall("bilibilihaahahaa")) # ['haa', 'haa']
p = re.compile(r'b{2,3}ili')
print(p.search("bbbilibbbilihaaha")) # 'bbbili'
print(p.findall("bbbilibbbilihaaha")) # ['bbbili', 'bbbili']
p = re.compile(r'bb{2,3}ili')
print(p.search("bbbilibbilihaaha")) # 'bbbili'
print(p.findall("bbbilibbilihaaha")) # ['bbbili']

总结:{m,n}元字符的用法是对其之前的正则表达式进行m到n次匹配,忽略m则下限默认为0,忽略n则上限默认为无穷大(逗号不能忽略)。

7、连接匹配符 —— |

p = re.compile(r'bi|a|ea|ut') 
print(p.search("bitbatbeatbut")) # 'bi'
print(p.findall("bitbatbeatbut")) # ['bi', 'a', 'ea', 'ut']

总结:|元字符的用法是将任意的正则表达式连接起来,比如A|B表示匹配正则表达式A或者B,一旦有一个先匹配成功,另外的就不会再进行匹配。
遗憾的是,这样的匹配并没有帮助我们把字符串中的单词提取出来,这需要后续的匹配规则共同发挥作用,让我们拭目以待。

8、万金油配符 —— \

p = re.compile(r'\.\n\t\f \{4,1}') 
print(p.search(".\n\t\f {4,1}")) # '.\n\t\x0c {4,1}'
print(p.findall(".\n\t\f {4,1}")) # ['.\n\t\x0c {4,1}']

上面的👆\当做转义字符使用

p = re.compile(r'\AA', flags=re.S)
print(p.search("A man has\nA beautiful bag.")) # 'A'
print(p.findall("A man has\nA beautiful bag.")) # ['A']

上面的👆\A只匹配字符串开始

p = re.compile(r'\ba\b') 
print(p.search("I have\ta\fbeautiful bag.a")) 
# <re.Match object; span=(7, 8), match='a'>
print(p.findall("I have\ta\fbeautiful bag.a")) # ['a', 'a']

上面的👆\b匹配空字符串,但只在单词开始或结尾的位置。这里的定义并不严格,后面有严格定义

p = re.compile(r'\Ba\B') 
print(p.search("I have\ta\fbeautiful bag.a")) 
# <re.Match object; span=(3, 4), match='a'>
print(p.findall("I have\ta\fbeautiful bag.a")) # ['a', 'a', 'a']

上面的👆\B匹配空字符串,但仅限于它 不在 单词的开头或结尾的情况,匹配的内容上与\b相同,而位置上互补

p = re.compile(r'\d')
print(p.search("I'm 21 years old.")) # '2'
print(p.findall("I'm 21 years old.")) # ['2', '1']
p = re.compile(r'\d+')
print(p.search("I'm 21 years old.")) # '21'
print(p.findall("I'm 21 years old.")) # ['21']
p = re.compile(r'\D+')
print(p.search("I'm 21 years old.")) # "I'm "
print(p.findall("I'm 21 years old.")) 
# ["I'm ", ' years old.']

上面的👆\d匹配一个数字字符,而\D匹配一个非数字字符

未完待续,敬请期待!👏👏👏🎉🎉🎉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值