「正则表达式」精讲

|----------------|
| 你好,我是安然无虞。 |

文章目录

正则表达式

正则表达式是一种高级文本处理技术, 通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的校验、字符串搜索、和替换操作.

正则表达式是一些由字符和特殊符号组成的字符串, 这些字符和特殊符号有特殊意义, 用来描述一个字符串的组成方式.

正则表达式语法

下面是正则表达式中常见的语法, 这里先了解, 后面会详细一一介绍.

表达式 描述
\d 数字
\D 非数字
\w 字母数字下划线
\s 所有空白符,包括换行符
\S 非空白符,不包括换行符
. 任意字符, 不包括换行符
[] 枚举, 匹配括号里出现的字符
[^] 枚举取反, 匹配中括号里出现的字符之外的字符
* 子表达式零次或多次
+ 子表达式一次或多次
? 子表达式零次或一次,或指明一个非贪婪限定符
\ 转义符
^ 匹配字符串的开头
$ 匹配字符串的结尾
{n,m}, {m} 匹配前面的子表达式至少n次,至多m次; 匹配前面的子表达式m次
|
() 分组
(?: ) 非捕获组
exp1(?=exp2) 匹配exp2 前面的 exp1
(?<=exp2)exp1 匹配 exp2 前面的 exp1
exp1(?!exp2) 匹配后面不是 exp2 的 exp1
(?<!exp2)exp1 匹配前面不是 exp2 的 exp1

贪婪和非贪婪

贪婪时候的运行情况:

python 复制代码
import re
# 贪婪
string = '打lol游戏, 打csgo游戏, 打格斗游戏, 打好玩的游戏'

result = re.search(r'打.*游戏', string)
print(result.group())

# 运行结果:
打lol游戏, 打csgo游戏, 打格斗游戏, 打好玩的游戏

非贪婪时候的运行情况:

python 复制代码
import re
# 非贪婪
string = '打lol游戏, 打csgo游戏, 打格斗游戏, 打好玩的游戏'

# 当?出现在形容数量的字符前, 代表该子表达式为 非贪婪.
# 没有❓就是贪婪.
# 形容数量的字符: .*  .+  .{m,n} ?
result = re.search(r'打.*?游戏', string)
print(result.group())

# 运行结果:
打lol游戏

+ 和 * 的区别

  • *: 匹配子表达式零次或多次
  • +: 匹配子表达式一次或多次
  • \s: 所有空白符, 包括换行符
python 复制代码
text = '正则表达式     很好用'
text2 = '正则表达式很好用'

for t in [text, text2]:
    regex = re.search(r'正则表达式\s*很好用', t)
    if regex:
        print(regex.group())
    else:
        print('未获取到')

运行结果是:

text 复制代码
正则表达式     很好用
正则表达式很好用

上面的代码如果使用的是 + , 则不会查找到 text2 文件.

re.search()

python 复制代码
import re
from re import I, M, S, X, DOTALL, L, U

"""
I: 忽略大小写.
M: 拆分行, 多行匹配. 这个是影响^$.
S: 忽略换行符. 也可以理解为使"."匹配所有字符.
X: 添加注释, 使正则表达式便于理解.
DOTALL: 等价于S.
U: python3不用了.
"""


text = '正则表达式是一种高级文本处理\n技术,通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的\n校验、字符串搜索、和替换操作'

result = re.search(pattern=r'.*', string=text, flags=re.S)
print(result.group())

运行结果是:

text 复制代码
正则表达式是一种高级文本处理
技术,通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的
校验、字符串搜索、和替换操作
python 复制代码
import re
from re import I, M, S, X, DOTALL, L, U

"""
I: 忽略大小写.
M: 拆分行, 多行匹配. 这个是影响^$.
S: 忽略换行符. 也可以理解为使"."匹配所有字符.
X: 添加注释, 使正则表达式便于理解.
DOTALL: 等价于S.
U: python3不用了.
"""


text = '正则表达式是一种高级文本处理\n技术,通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的\n校验、字符串搜索、和替换操作'

result = re.findall(r'^.*$', text, re.M)
print(result)

result = re.findall(r'.*', text, re.S)
print(result)

运行结果是:

text 复制代码
['正则表达式是一种高级文本处理', '技术,通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的', '校验、字符串搜索、和替换操作']

['正则表达式是一种高级文本处理\n技术,通过特征匹配、特征搜索和特征替换功能完成对复杂字符串的\n校验、字符串搜索、和替换操作', '']

好的, 注意理解上面运行结果的区别.

re.split()

re.split()方法的作用类似于Python中的split(), 但是功能比Python中的多.

python 复制代码
import re

# 需求: 根据逗号拆分为有多个元素的列表.
text = '安然, 老板, 芜湖, 周杰伦'
print(text.split(','))


# # 需求: 根据特殊符号拆分有多个元素的列表.
text2 = '安然, 老板; 芜湖. 周杰伦'
regex = re.split(r'[,;.]', text2)
print(regex)

运行结果是:

text 复制代码
['安然', ' 老板', ' 芜湖', ' 周杰伦']
['安然', ' 老板', ' 芜湖', ' 周杰伦']

匹配和替换

python 复制代码
import re

text = '11122233303334455566789K'
regex = re.compile(r'\d*')

# match实际上等价于search的表达式前面加上^(从开头匹配)
result = regex.match(text)
print(result.group())

# fullmatch实际上等价于search的表达式前面加上^, 结尾加上$(匹配开头结尾)
result2 = regex.fullmatch(text)
print(result2.group())

result3 = re.search(r'^\d*$', text)
if result3:
    print(1111)

result4 = regex.sub('a', text)
print(result4)

运行结果是:

text 复制代码
11122233303334455566789

报错: AttributeError: 'NoneType' object has no attribute 'group'

没有打印1111

aaKa

re.findall() 和 re.finditer()

在当前目录下有个kyj.txt文件, 里面有这么一篇关于孔乙己的文章:

text 复制代码
	鲁镇的酒店的格局,是和别处不同的:都是当街一个曲尺形的大柜台,柜里面预备着热水,可以随时温酒。做工的人,傍午傍晚散了工,每每花四文铜钱,买一碗酒,------这是二十多年前的事,现在每碗要涨到十文,------靠柜外站着,热热的喝了休息;倘肯多花一文,便可以买一碟盐煮笋,或者茴香豆,做下酒物了,如果出到十几文,那就能买一样荤菜,但这些顾客,多是短衣帮⑴,大抵没有这样阔绰⑵。只有穿长衫的,才踱进店面隔壁的房子里,要酒要菜,慢慢地坐喝。
	我从十二岁起,便在镇口的咸亨酒店里当伙计,掌柜说,样子太傻,怕侍候不了长衫主顾,就在外面做点事罢。外面的短衣主顾,虽然容易说话,但唠唠叨叨缠夹不清的也很不少。他们往往要亲眼看着黄酒从坛子里舀出,看过壶子底里有水没有,又亲看将壶子放在热水里,然后放心:在这严重监督下,羼⑶水也很为难。所以过了几天,掌柜又说我干不了这事。幸亏荐头⑷的情面大,辞退不得,便改为专管温酒的一种无聊职务了。
	我从此便整天的站在柜台里,专管我的职务。虽然没有什么失职,但总觉得有些单调,有些无聊。掌柜是一副凶脸孔,主顾也没有好声气⑸,教人活泼不得;只有孔乙己到店,才可以笑几声,所以至今还记得。
孔乙己是站着喝酒而穿长衫的唯一的人。他身材很高大;青白脸色,皱纹间时常夹些伤痕;一部乱蓬蓬的花白的胡子。穿的虽然是长衫,可是又脏又破,似乎十多年没有补,也没有洗。他对人说话,总是满口之乎者也⑹,教人半懂不懂的。因为他姓孔,别人便从描红纸上的"上大人孔乙己⑺"这半懂不懂的话里,替他取下一个绰号,叫作孔乙己。孔乙己一到店,所有喝酒的人便都看着他笑,有的叫道,"孔乙己,你脸上又添上新伤疤了!"他不回答,对柜里说,"温两碗酒,要一碟茴香豆。"便排出九文大钱。他们又故意的高声嚷道,"你一定又偷了人家的东西了!"孔乙己睁大眼睛说,"你怎么这样凭空污人清白......""什么清白?我前天亲眼见你偷了何家的书,吊着打。"孔乙己便涨红了脸,额上的青筋条条绽出,争辩道,"窃书不能算偷......窃书!......读书人的事,能算偷么?"接连便是难懂的话,什么"君子固穷⑻",什么"者乎"之类,引得众人都哄笑起来:店内外充满了快活的空气。
	听人家背地里谈论,孔乙己原来也读过书,但终于没有进学⑼,又不会营生⑽;于是愈过愈穷,弄到将要讨饭了。幸而写得一笔好字,便替人家钞⑾钞书,换一碗饭吃。可惜他又有一样坏脾气,便是好喝懒做。坐不到几天,便连人和书籍纸张笔砚,一齐失踪。如是几次,叫他抄书的人也没有了。孔乙己没有法,便免不了偶然做些偷窃的事。但他在我们店里,品行却比别人都好,就是从不拖欠;虽然间或没有现钱,暂时记在粉板上,但不出一月,定然还清,从粉板上拭去了孔乙己的名字。
	孔乙己喝过半碗酒,涨红的脸色渐渐复了原,旁人便又问道,"孔乙己,你当真认识字么?"孔乙己看着问他的人,显出不屑置辩的神气。他们便接着说道,"你怎的连半个秀才也捞不到呢?"孔乙己立刻显出颓唐不安模样,脸上笼上了一层灰色,嘴里说些话;这回可是全是之乎者也之类,一些不懂了。在这时候,众人也都哄笑起来:店内外充满了快活的空气。
	在这些时候,我可以附和着笑,掌柜是决不责备的。而且掌柜见了孔乙己,也每每这样问他,引人发笑。孔乙己自己知道不能和他们谈天,便只好向孩子说话。有一回对我说道,"你读过书么?"我略略点一点头。他说,"读过书,......我便考你一考。茴香豆的茴字,怎样写的?"我想,讨饭一样的人,也配考我么?便回过脸去,不再理会。孔乙己等了许久,很恳切的说道,"不能写罢?......我教给你,记着!这些字应该记着。将来做掌柜的时候,写账要用。"我暗想我和掌柜的等级还很远呢,而且我们掌柜也从不将茴香豆上账;又好笑,又不耐烦,懒懒的答他道,"谁要你教,不是草头底下一个来回的回字么?"孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说,"对呀对呀!......回字有四样写法⑿,你知道么?"我愈不耐烦了,努着嘴走远。孔乙己刚用指甲蘸了酒,想在柜上写字,见我毫不热心,便又叹一口气,显出极惋惜的样子。
	有几回,邻居孩子听得笑声,也赶热闹,围住了孔乙己。他便给他们茴香豆吃,一人一颗。孩子吃完豆,仍然不散,眼睛都望着碟子。孔乙己着了慌,伸开五指将碟子罩住,弯腰下去说道,"不多了,我已经不多了。"直起身又看一看豆,自己摇头说,"不多不多!多乎哉?不多也⒀。"于是这一群孩子都在笑声里走散了。
	孔乙己是这样的使人快活,可是没有他,别人也便这么过。
	有一天,大约是中秋前的两三天,掌柜正在慢慢的结账,取下粉板,忽然说,"孔乙己长久没有来了。还欠十九个钱呢!"我才也觉得他的确长久没有来了。一个喝酒的人说道,"他怎么会来?......他打折了腿了。"掌柜说,"哦!""他总仍旧是偷。这一回,是自己发昏,竟偷到丁举人家里去了。他家的东西,偷得的么?""后来怎么样?""怎么样?先写服辩⒁,后来是打,打了大半夜,再打折了腿。""后来呢?""后来打折了腿了。""打折了怎样呢?""怎样?......谁晓得?许是死了。"掌柜也不再问,仍然慢慢的算他的账。
	中秋之后,秋风是一天凉比一天,看看将近初冬;我整天的靠着火,也须穿上棉袄了。一天的下半天,没有一个顾客,我正合了眼坐着。忽然间听得一个声音,"温一碗酒。"这声音虽然极低,却很耳熟。看时又全没有人。站起来向外一望,那孔乙己便在柜台下对了门槛坐着。他脸上黑而且瘦,已经不成样子;穿一件破夹袄,盘着两腿,下面垫一个蒲包,用草绳在肩上挂住;见了我,又说道,"温一碗酒。"掌柜也伸出头去,一面说,"孔乙己么?你还欠十九个钱呢!"孔乙己很颓唐的仰面答道,"这......下回还清罢。这一回是现钱,酒要好。"掌柜仍然同平常一样,笑着对他说,"孔乙己,你又偷了东西了!"但他这回却不十分分辩,单说了一句"不要取笑!""取笑?要是不偷,怎么会打断腿?"孔乙己低声说道,"跌断,跌,跌......"他的眼色,很像恳求掌柜,不要再提。此时已经聚集了几个人,便和掌柜都笑了。我温了酒,端出去,放在门槛上。他从破衣袋里摸出四文大钱,放在我手里,见他满手是泥,原来他便用这手走来的。不一会,他喝完酒,便又在旁人的说笑声中,坐着用这手慢慢走去了。
	自此以后,又长久没有看见孔乙己。到了年关⒂,掌柜取下粉板说,"孔乙己还欠十九个钱呢!"到第二年的端午,又说"孔乙己还欠十九个钱呢!"到中秋可是没有说,再到年关也没有看见他。
	我到现在终于没有见------大约孔乙己的确死了。 [2]
	一九一九年三月。

我们来提取其中关于 孔乙己 的信息:

python 复制代码
import re

with open("./kyj.txt", 'r', encoding='utf-8') as f:
    text = f.read()

# 孔乙己出现的次数.
result_nums = re.findall(r'孔乙己', text, re.S)
print(result_nums)

# 使用dir()可以查看到对象可调用的方法
print(dir(result_nums))
# 因为该对象有__len__方法, 所以可以使用
print(len(result_nums))

运行结果是:

text 复制代码
['孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己', '孔乙己']

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

33

将上述re.findall()改为 re.finditer()看看有何变化:

python 复制代码
import re

with open("./kyj.txt", 'r', encoding='utf-8') as f:
    text = f.read()

"""
I: 忽略大小写.
M: 拆分行, 多行匹配. 这个是影响^$.
S: 忽略换行符. 也可以理解为使"."匹配所有字符.
X: 添加注释, 使正则表达式便于理解.
DOTALL: 等价于S.
U: python3不用了.
"""

result_list = re.finditer(r'孔乙己', text, re.S)
print(result_list)
print(dir(result_list))
for result in result_list:
    print(result.group())

运行结果是:

text 复制代码
<callable_iterator object at 0x7fe9e80f84f0>

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

孔乙己
孔乙己
孔乙己
...

ok, 看看下面的代码:

python 复制代码
import re

string = 'testesttestest'
test_count = re.findall(r'test', string)

print(test_count)
print(len(test_count))

运行结果是:

text 复制代码
['test', 'test']
2

可能有的老铁会说为什么不是 4 呢?

这是因为 正则表达式的引擎是一直朝后面找, 不会再向前匹配.

但是这里扩展一下, 有个第三方库 regex, 它是对 re 的扩展, 里面实现了重叠匹配机制:

python 复制代码
import regex

string = 'testesttestest'
# overlapped 重叠匹配.
test_count = regex.findall(r'test', string, overlapped=True)
print(test_count)
print(len(test_count))

运行结果是:

text 复制代码
['test', 'test', 'test', 'test']
4

如果将 regex.findall(overlapped=True) 改为 regex.findall(overlapped=False) , 就实现了和 re.findall()一样的功能, 都是不会进行重叠匹配的.

可选字符

python 复制代码
import re
# 问号的含义: 子表达式零次或一次
# 加号的含义: 子表达式一次或多次
# 星号的含义: 子表达式零次或多次
string1 = 'color'
string2 = 'colour'

for text in [string1, string2]:
    regex = re.search(r'colou?r', text)
    if regex:
        print(regex.group())
    else:
        print('未匹配到')

运行结果是:

text 复制代码
color
colour

如果改为 + 号 regex = re.search(r'colou+r', text), 看看执行结果:

text 复制代码
未匹配到
colour

枚举

  • []: 枚举, 匹配括号里出现的字符
  • [^]: 枚举取反, 匹配括号里出现的字符之外的字符
  • \ : 转义符
  • 加号的含义: 子表达式一次或多次
python 复制代码
# 正则里有含义的特殊符号.
string1 = '1114564678122-11212]'

regex1 = re.search(r'[0-9]+', string1)
print(regex1.group())

运行结果是:

text 复制代码
1114564678122

如果想匹配所以呢, 包括后面的 -11212]:

python 复制代码
# 正则里有含义的特殊符号.
string1 = '1114564678122-11212]'

regex1 = re.search(r'[0-9-\]]+', string1)
print(regex1.group())

运行结果是:

text 复制代码
1114564678122-11212]

关于 特殊枚举, 比如中文相关的:

说明: 中文字符集的第一个字编码和最后一个字的编码范围是:

​ [\u4e00-\u9fa5]

python 复制代码
# 特殊枚举: 中文 [\u4e00-\u9fa5]
string2 = '长风破浪会有时,直挂云帆济沧海.'
# \s: 所有空白符,包括换行符
regex2 = re.search(r'[\u4e00-\u9fa5,\s\.]+', string2)

print(regex2.group())

运行结果是:

text 复制代码
长风破浪会有时,直挂云帆济沧海.

关于枚举取反, 比例这里的匹配中文之外的所有字符:

python 复制代码
# 枚举取反.
string3 = '正则表达式是一种高级文本处理技术(Regular expression is an advanced text processing technology)'
regex3 = re.search(r'[^\u4e00-\u9fa5]+', string3)

print(regex3.group())

运行结果是:

text 复制代码
(Regular expression is an advanced text processing technology)

|: 或

(): 分组

前面我们有讲到 枚举, 我们看看下面的代码用枚举执行的情况:

python 复制代码
import re

text_list = [
    'wlb.avi',
    'wlb.mp4',
    'wlb.jpg',
    'wlb.zip',
    'wlb.txt',
    'wlb.xls',
]

for text in text_list:
    regex = re.search(r'wlb\.[avimp4jgztxls]+', text)
    if regex:
        print(regex.group())

运行结果是:

text 复制代码
wlb.avi
wlb.mp4
wlb.jpg
wlb.zip
wlb.txt
wlb.xls

我们看到这里用枚举来做也是能得到结果的, 但是实际网页中的数据不一定都是对的, 可能会出现错误的脏数据, 比如把后缀.mp4错写成了.mpt文件:

python 复制代码
import re

text_list = [
    'wlb.avi',
    'wlb.mp4',
    'wlb.jpg',
    'wlb.zip',
    'wlb.txt',
    'wlb.xls',
    'wlb.mpt',
]

for text in text_list:
    regex = re.search(r'wlb\.[avimp4jgztxls]+', text)
    if regex:
        print(regex.group())

这个时候用枚举的话也是查到了脏数据:

text 复制代码
wlb.avi
wlb.mp4
wlb.jpg
wlb.zip
wlb.txt
wlb.xls
wlb.mpt

所以这里我们使用 或 来对上面的代码进行改写:

python 复制代码
import re

text_list = [
    'wlb.avi',
    'wlb.mp4',
    'wlb.jpg',
    'wlb.zip',
    'wlb.txt',
    'wlb.xls',
    'wlb.mpt',
]

for text in text_list:
    regex = re.search(r'wlb\.(avi|mp4|jpg|zip|txt|xlsx?)', text)
    if regex:
        print(regex.group())

运行结果是:

text 复制代码
wlb.avi
wlb.mp4
wlb.jpg
wlb.zip
wlb.txt
wlb.xls

这里我们补充说明一下 或 的执行机制:

python 复制代码
text2 = '世界知名逆向工程师刘老板'

result = re.search(r'世界知名逆向工程师(刘老|刘|刘老板)', text2)
print(result.group())

运行结果是:

text 复制代码
世界知名逆向工程师刘老

所以, 或的执行机制是: 找到了想要的数据时就不会再继续向后匹配了.

分组

python 复制代码
import re

text_list = ['2022-06-12', '2021 07 08', '2019/05/06']

# 匹配年月日, **单独** 把年,月,日拿出来.
for text in text_list:
    regex = re.search(r'(\d{4})[-\s/](\d{2})[-\s/](\d{2})', text)
    print(regex.group(1))
    print(regex.group(2))
    print(regex.group(3))
    print('=' * 50)

运行结果是:

text 复制代码
2022
06
12
==================================================
2021
07
08
==================================================
2019
05
06
==================================================

非捕获组

  • (?😃: 非捕获组

非捕获组在数据清洗工作中用的还是很多的.

下面有一个需求: 只要域名, 不要协议.

python 复制代码
import re

text1_list = [
    'https://www.baidu.com/',
    'http://www.baidu.com/'
]
for text1 in text1_list:
    regex = re.search(r'(https://|http://)(www.*?$)', text1)
    print(regex.group(1))
    print(regex.group(2))

运行结果是:

text 复制代码
https://
www.baidu.com/
http://
www.baidu.com/

下面我们用 非捕获组 对上面的代码进行改写:

python 复制代码
text1_list = [
    'https://www.baidu.com/',
    'http://www.baidu.com/'
]
for text1 in text1_list:
    regex = re.search(r'(?:https://|http://)(www.*?$)', text1)
    print(regex.group(1))

这是 协议 那组数据就不会被捕获, 所以域名就是第一组.

分组的引用

这也是比较常见的用法, 看看下面的代码:

python 复制代码
# 分组的引用
text2_list = [
    '<div>王老板</div>',
    '<div>王老板</img>'
]
# 需求,只要正确的标签.
for text2 in text2_list:
    regex = re.search(r'<(\w+)>[\u4e00-\u9fa5]+</\1>', text2)
    print(regex.group())

注意对上面代码中 </\1>的理解, 就是 第一个分组的引用.

所以, 这里的运行结果是:

text 复制代码
<div>王老板</div>

AttributeError: 'NoneType' object has no attribute 'group'

现在还有一个需求: 不管下面这个人是谁, 都要他是永远的厉害.

python 复制代码
text3_list = [
    '王老板永远的厉害',
    '张老板永远的无敌',
    '李老板永远的爹'
]

# 需求: 我不管这个人是谁 ,我都要他是永远的厉害.
for text3 in text3_list:
  	# re.sub(): 替换
    regex = re.sub(r'(.老板永远的)(.*?)$', r'\1厉害', text3)
    print(regex)

运行结果是:

text 复制代码
王老板永远的厉害
张老板永远的厉害
李老板永远的厉害

断言

正则表达式的引擎, 匹配过的地方叫后, 未匹配过的地方叫前.

比如: 我爱你, 现在查到了 爱, 那么 我 就是后(匹配过的), 你 就是前(未匹配的).

后顾

匹配过的地方叫后.

python 复制代码
# 后顾(?<=), 负后顾(?<!) 从右往左看.

# 前瞻后顾帮助正则判断, 不匹配. 不会把前瞻后顾的值给你. 可以理解为 非捕获.
text_list = [
    '美女请王老板吃饭',
    '畜生请王老板吃饭',
    '妹妹请王老板吃饭',
    '卞大请王老板吃饭',
]
# 我们不想要畜生来请王老板吃饭, 所以这里是负后顾.
for text in text_list:
    regex = re.search(r'(?<!畜生)请王老板吃饭', text)
    # 或者也可以这样写:
    # regex = re.search(r'(?<=美女|妹妹|卞大)请王老板吃饭',text)
    if regex:
        print(regex.group())
    else:
        print(f'没匹配到: {text}')

运行结果是:

text 复制代码
请王老板吃饭
没匹配到: 畜生请王老板吃饭
请王老板吃饭
请王老板吃饭

如果我们只要 妹妹请王老板吃饭, 那么可以这样改写代码:

python 复制代码
regex = re.search(r'(?<=妹妹)请王老板吃饭', text)

注意后顾有一个坑就是:

比如前面的代码中:

python 复制代码
regex = re.search(r'(?<=美女|妹妹|卞大)请王老板吃饭',text)

这里的 美女、妹妹、卞大 长度是一样的, 是没有问题的, 如果长度不一致, 会出现异常报错:

python 复制代码
regex = re.search(r'(?<=美女子|妹妹好|卞大)请王老板吃饭',text)

这样是不对的, 所以我们在用后顾的时候, 要特别注意这一点.

前瞻

未匹配过的地方叫前.

比如这里有一个需求: 只允许国内的网站注册.

python 复制代码
# 前瞻(?=), 负前瞻(?!) 从左往右看.

text2_list = [
    'aaabbbccc@qq.com',
    'aaabbbccc@163.com',
    'aaabbbccc@gmail.com',
]
# 需求: 只需要国内的网站注册.
for text2 in text2_list:
    # regex = re.search(r'\w+@(?=qq|163).*?$', text2)
    regex = re.search(r'\w+@(?!gmail).*?$', text2)
    if regex:
        print(regex.group())

现在增加难度, 需求是: 切分公司, 要求括号里的不切.

python 复制代码
text3 = '王老板有限公司,a.b集团有限公司.拜读有限公司(曾用名ccc有限公司,co,.ltd),yrx有限公司.'
name_list = re.split(r'(?<![a-z,])[,.](?![^\(]*\))', text3)
print(name_list)

运行结果是:

text 复制代码
['王老板有限公司', 'a.b集团有限公司', '拜读有限公司(曾用名ccc有限公司,co,.ltd)', 'yrx有限公司', '']

回溯

回溯指的是 正则表达式的执行机制, 过程, 这里只做了解.

python 复制代码
string = 'aaaaab'

# 同样的结果, 性能有差距.
regex1 = re.compile(r'a+b')  # 0次回溯.
regex2 = re.compile(r'a.*ab')  # 2次回溯.
print('1:', regex1.search(string).group())
print("2:", regex2.search(string).group())

为什么 r'a+b' 是0次回溯:

因为执行步骤是先执行 a, 然后执行+, 找到 aaaa, 然后匹配到b, 没有回溯.

为什么 r'a.*ab' 是2次回溯:

因为先 执行 a, 然后执行 .*找到后面所有的字符, 也就是aaaab, 后面执行的a和b会有2次回溯.

|----------------------|
| 遇见安然遇见你,不负代码不负卿。 |
| 谢谢老铁的时间,咱们下篇再见~ |

相关推荐
黎子越2 小时前
python循环相关联系
开发语言·python·算法
期末考复习中,蓝桥杯都没时间学了2 小时前
正则表达式相关知识点
正则表达式
csbysj20202 小时前
DOM 解析器错误
开发语言
Дерек的学习记录2 小时前
二叉树(下)
c语言·开发语言·数据结构·学习·算法·链表
葡萄成熟时 !2 小时前
JDK时间类
java·开发语言
气派飞鹰2 小时前
windows下C++个人开发最佳实践(CMake+vcpkg+trae)
开发语言·c++·个人开发
余瑜鱼鱼鱼2 小时前
Thread类中run和start的区别
java·开发语言·前端
n 55!w !1082 小时前
js练习作业
开发语言·javascript·ecmascript
Whisper_Sy2 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 月报告实现
android·开发语言·javascript·网络·flutter·ecmascript