Python系列教程
第1章 Python 语言概述
Python编程规范+最佳实践
python文件:.py,.ipynb, pyi, pyc, pyd, pyo都是什么文件?
第1章必背的内容
第2章 Python基础
字符串切片
Python数据类型大战:可变与不可变,谁主沉浮?
Python「布尔类型」:不只是True和False!
Python「枚举类型」:你真的了解枚举类型吗?
Python字符串格式化:哪种字符串格式化方法最快?
Python字符串编码:为什么你的网页显示乱码?
Python「内置变量」:不只是变量,更是编程的魔法!
Python变量的作用域,你真的了解吗?
Python中如何使用F-Strings格式化浮点数
第2章必备的内容
第3章 流程控制和异常处理
加速Python for循环
for循环
Python三元表达式:让代码简洁与效率提升成为可能
Python「While循环」:何时打破循环?
Python「异常处理」:程序出错了?不慌
这样可以减少IF语句的使用,从此告别if-else噩梦
Python循环加速的秘方,可提速上千倍!
如何在Python中优雅地使用断言?这篇文章给你答案!
Python异常处理:12个异常处理技巧,你掌握了几个?
Python中的and和or你真的会用吗,代码逻辑竟然可以如此简单!
Python的else子句7个妙用,原来还能这样用,整挺好!
快来看!Python写代码,没有pass怎么行?
第三章 必背的内容
第4章 高级数据结构
Python字典嵌套:编程界的俄罗斯套娃
Python「类型注解」:如何让你的Python代码够清晰?
列表越界了?来学学Python列表的花式操作!
Python字典的这些黑科技,你用过几个?
Python元组:为何你的代码需要这个'不变'的伙伴?
试试Python具名元组NamedTuple吧!用过的都说好
Python中的5种队列数据结构,你知道几个?
itertools模块让你的代码原地起飞!
第5章 正则表达式
正则表达式
本文档使用 MrDoc 发布
-
+
首页
第5章 正则表达式
  本章讲解正则表达式的概念,基础知识、普通字符正则表达式、特殊字符正则表达式、re模块的用法等。正则表达式是一种特定规则的字符查找方法,可以根据这个规则在指定的语句中查找符合要求的字符,并且进行替换,简而言之,正则表达式就是用来检索、替换那些符合某个规则的文本。   通过本章的学习,可以清晰地了解正则表达式的概念与使用场景,明白正则表达式对于数据清洗和数据筛选的作用,同时对数据采集的职业前景有更加清晰的认知。 # 5.1 正则表达式   在学习正则表达式之前,首先需要掌握一些常用的正则语法。正则语法主要包含对数字、字母、符号等文本内容进行匹配。这些正则匹配语法在任何文件中都可以使用。在Python开发中,借助re模块来实现正则表达式的全部功能。 ## 5.1.1 正则表达式语法 表5- 1 正则符号的含义 | 语法 | 说明 | 表达式示例 | 匹配的字符串 | | --- | --- | --- | --- | | `^` | 匹配字符串的开始位置 | ^python | python… | | `$` | 匹配字符串的结束位置 | python$ | …python | | `*` | 匹配前一个字符出现0次或者至少1次 | python* | pytho/python/pythonn | | `+` | 匹配前一个字符出现至少1次 | python+ python/pythonn/pythonnn | | `?` | 匹配前一个字符出现0次或者1次 | python? | pytho/python | | `{m}` | 匹配前一个字符连续出现m次 | p{3}ython | pppython | | `{m,n}` | 匹配前一个字符连续出现最少m次最多n次 | p{1,3}ython python/ppython/pppython | | `\|` | 子表达式之间“或”关系匹配 | python\|java | python/java | | `[]` | 匹配[]中的任意一个字符 | [abc] | a/b/c | | `[0-9]` | 匹配0-9中任意一个数字 | [0-5] | 0/1/2/3/4/5 | | `[a-z]` | 匹配a-z中任意一个小写字母 | [a-z] | a/b/c/j/k/l/o/p | | `[A-Z]` | 匹配A-Z中任意一个大写字母 | [A-Z] | A/B/C/F/G/H/Y/K/M/N | | `[^a-z]` | 匹配除小写字母外任意一个字符 | [^a-z] | A/B/^ | | `.` | 匹配除了换行符“\n”以外的任意一个字符 | p.n | pan/pbn/pcn/pdn | | `\` | 转移字符,使后一个字符串改变原来意思,比如要匹配“*”,就需要写成“\*” | ab\* | ab* | | `\d` | 匹配任意一个数字,相当于[0-9] | a\d | a1/a2/a3/a4 | | `\D` | 匹配任意一个非数字字符,相当于\d的取反,即[^0-9] | a\D | ab/ac/ad/ak/am | | `\s` | 匹配任意空白字符 | a\sb | a b/a b | | `\S` | 匹配任意非空白字符 | a\Sb | acb/atb/apb | | `\w` | 匹配任意一个字母或数字或下画线 | a\wk | aak/a5k/a_k/a9k | | `()` | 匹配分组,即把括号中的字符当作一个整体进行匹配 | (abc){2} | Abcabc |   【示例5-1】使用分组匹配年月日   以日期为示例。假设格式是yyyy-mm-dd的,我们可以先写一个简单的正则   `\d`代表匹配一个数字 `regex = "/\d{4}-\d{2}-\d{2}/"`   然后再修改成括号版的 `regex_1 = "/(\d{4})-(\d{2})-(\d{2})/"`   通过表5-1和示例5-1可以看出,上面这些正则匹配规则只是针对单一字符,在实际应用中是对多个单一字符匹配的组合,因此建议读者认真掌握,便于在python开发的时候能信手拈来。对于这么多的正则匹配规则学习起来肯定是枯燥无味的,接下来将进行python中re模块的讲解与使用,便于读者消化吸收。 ## 5.1.2 re模块方法的使用   Python在安装完成以后,就已经内置re模块,不需要通过“pip”进行下载,可以在Python安装位置下的Lib目录中找到re.py文件,即re模块。在使用时通过import导入即可,查看re版本及属性方法的方式如下: ```python import re print(re.__version__) # 查看re版本 print(re.__all__) # 查看re模块的所有属性方法 ```   运行结果: ```python 2.2.1 ['match', 'fullmatch', 'search', 'sub', 'subn', 'split', 'findall', 'finditer', 'compile', 'purge', 'template', 'escape', 'error', 'Pattern', 'Match', 'A', 'I', 'L', 'M', 'S', 'X', 'U', 'ASCII', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL', 'VERBOSE', 'UNICODE'] ```   re模块主要包含编译正则表达式的函数和各种匹配函数。 表5-2 re模块下的常用函数 | 函数 | 函数描述 | | --- | --- | | complie(pattern) | 创建模式对象 | | match(pattern,string) | 在字符串开始处匹配模式 | | search(pattern,string) | 在字符串中寻找匹配模式 | | splite(pattern,string) | 根据模式分割字符串 | | findall(pattern,string) | 列表返回形式匹配项 | | sub(pat,repl,string) | pat匹配项用repl替换 | ### 1.compile()函数   `功能`:将正则表达式的样式编译为一个正则表达式对象(正则对象)。   `语法格式`:`complie(pattern,filename,mode,flags,dont_inherit)`   参数说明: - `pattern`:字符串或者语法树对象; - `filename`:代码文件名称; - `mode`:指定编译代码的种类。可以指定为exec,eval,single; - `flags和dont_inherit`:可选参数,极少使用。   `返回值`:返回表达式执行的结果。   【示例5-2】使用compile函数创建正则匹配对象 ```python import re pat=re.compile('A') # 创建模式对象 m=pat.search('CBA') # 在字符串中寻找符合模式对象的部分 print(m) ```   运行结果 ```python <re.Match object; span=(2, 3), match='A'> ```   运行结果表示:匹配到了,返回`MatchObject(True)`。 ```python m=pat.search('CBD') print(m) ```   运行结果 ```python None ```   表示没有匹配到,返回`None(False)`。 ```python re.search('A','CBA') ```   运行结果 <_sre.SRE_Match object at 0xb72cd170>   运行结果表示:匹配到了,返回`MatchObject(True)`。   `注意`:根据上面的示例,我们发现使用`compile()`方法创建模式对象和直接使用`search()`方法得到的结果是一致的,建议使用第一种方法。   将正则表达式转换为模式对象,能实现更有效率的匹配,因为其他的函数会在内部进行转换。   【示例5-3】使用compile函数创建模式对象 ```python import re # re.I表示忽略大写 pattern = re.compile(r"([a-z]+) ([a-z]+)",re.I) # 按正则表达式匹配字符串 m = pattern.match("Hello Python,Nice to meet you!") print(m.group()) # 返回成功匹配的字符串 ```   运行结果 ```python Hello Python ``` ### 2.match()函数   `功能`:从字符串的开始位置进行正则模式的匹配,在起始位置匹配成功则返回匹配的对象实例,否则返回None。   语法格式:`re.match(pattern,string,flag=0)`   参数说明   `pattern`:匹配的正则表达式;   `string`:要进行匹配的字符串;   `flags`:标志位,用于控制正则表达式的匹配方式,例如是否区分大小写,多行匹配等。   我们可以使用`group(num)`或者`groups()`匹配对象函数来获取匹配表达式。   `group(num=0)`获取匹配结果的各个分组的字符串,`group()`可以一次输入多个组号,此时返回一个包含那些组所对应值的元组。`groups`则返回一个包含所有分组字符串的元组。   `注意`:如果未匹配成功,`match()`返回值为`None`,此时再使用`group()`、`groups()`方法会报错。应该先获取匹配对象,然后判断匹配对象是否为空,当非空时再使用`group()`,`groups()`方法获取匹配的结果。   【示例5-4】使用match函数从开始位置匹配指定字符 ```python import re str1 = "How are you." pattern_1 = re.compile(r"How") # 创建正则匹配对象 pattern_2 = re.compile(r"are") # 使用match从开始位置进行匹配 result_1 = re.match(pattern_1,str1) result_2 = re.match(pattern_2,str1) print("result_1的结果是",result_1.group()) print("result_2的结果是",result_2) ```   运行结果 ```python result_1的结果是 How result_2的结果是 None ``` ### 3.search()函数   `功能`:在整个目标字符串中进行查找并返回成功匹配的第一个字符串,若匹配成功则返回匹配的对象实例,否则返回None。   `语法`:`re.search(pattern,string,flags=0)`   参数说明:同`match()`函数   【示例5-5】使用search函数从任意位置匹配指定字符 ```python import re str1 = "Welcome to China!" pattern_1 = re.compile(r"China") # 创建正则匹配对象 # 使用search在任意位置进行匹配 result_1 = re.search(pattern_1,str1) print("result_1的结果是",result_1.group()) ```   运行结果 ```python result_1的结果是 China ```   `说明`:`match()`函数只能在字符串开始位置进行匹配,而`search()`函数会在整个字符串内查找匹配。 ### 4.findall()函数   `功能`:在目标字符串中查找所有符合正则匹配模式的字符串,并这些符合要求的字符串放在列表中进行返回,查找不到则返回`None`。   `语法`:`re.findall(pattern,string,flags=0)`   参数说明:同`match()`函数   【示例5-6】使用findall函数匹配所有符合条件的字符 ```python import re str1="abcd_ABCD_abcd_123_a1b2c3" # 创建匹配对象,匹配规则是ab或者AB pattern_1 = re.compile(r"ab|AB") # 使用findall()方法查找所有符合条件的字符串 result_1 = re.findall(pattern_1,str1) print(result_1) # 创建正则对象,匹配规则为匹配所有的数字 # \d匹配数字 \d+表示匹配多个数字 pattern_2 = re.compile(r"\d+") result_2 = re.findall(pattern_2,str1) print(result_2) ```   运行结果 ```python ['ab', 'AB', 'ab'] ['123', '1', '2', '3'] ``` ### 5.sub()函数   `sub()`函数和`subn()`函数都是根据正则匹配模式进行替换,将某个字符串中所有符合正则匹配模式的字符串进行指定字符的替换。`sub()`函数返回一个替换后的字符串,`subn()`函数还可以返回一个替换的总次数,替换后的字符串和次数形成一个元组进行返回。   `语法`: ```python re.sub(pattern,repl,string,count) re.subn(pattern,repl,string,count) ```   `参数说明`:   `pattern`:正则表达式匹配模式;   `repl`:要替换成的内容;   `string`:进行替换内容的字符串;   `count`:可选参数,设置最大替换次数。   【示例5-7】使用sub函数和subn函数进行正则匹配和替换 ```python import re str = "2022,Hello Python, you are my friend!" pattern = re.compile(r"\d+") result_1 = re.sub(pattern,"2023",str) print(result_1) result_2 = re.subn(pattern,"2023",str) print(result_2) ```   运行结果 ```python 2023,Hello Python, you are my friend! ('2023,Hello Python, you are my friend!', 1) ``` ### 6.split()函数   `re`模块的`split()`方法与字符串的`split()`方法相似,前者是根据正则表达式分割字符串,与后者相比,显著提升了字符分割能力,如果没有使用特殊符号表示正则表达式来匹配多个模式,那么`re.split()`和`string.split()`功能是一样的。   `语法`:`re.split(pattern,string)`   `参数说明`:   `pattern`:正则表达式匹配模式;   `string`:要进行分割的目标字符串。   【示例5-8】使用split函数进行正则分割 ```python import re str = "a12b34c67d89e" # \d{2}代表匹配两个字符 pattern = re.compile("\d{2}") result = re.split(pattern,str) print(result) ```   运行结果 ```python ['a', 'b', 'c', 'd', 'e'] ``` ## 5.1.3 正则表达式应用   项目一:电话号码提取程序   假设你的上级领导给安排一个任务,在一个txt文件中找出所有的电话号码。如果采用人工方法一个一个去找,可能需要查找很长时间。这个时候,如果有一个程序,可以自动的去匹配文件中的电话号码,将大大提高工作效率,请你帮助上级领导排忧解难。   分析:当开始接手一个新项目时,不要直接写代码。建议先草拟高层次的计划,弄清楚程序需要做什么,需要哪些函数方法,需要分为几部分来实现。   现在可以开始思考,如何用代码来完成工作。代码需要做下面的事情: (1) 创建一个正则表达式,一个匹配电话号码。 (2) 对于正则表达式,找到所有的匹配,而不只是第一次匹配。 (3) 将匹配的字符串整理好格式,放在一个字符串中。 (4) 如果文本中没有找到匹配,显示某种消息。   这个列表就像项目的路线图。在编写代码时,可以独立地关注其中的每一步。   第1步:为电话号码创建一个正则表达式   需要创建一个正则表达式来查找电话号码。创建一个新文件,输入以下代码,保存为 GetPhone.py ```python import re regx = re.compile(r'(1\d{10})', re.S) ```   1代表匹配以数字1开始,后面跟连续10个数字的字符,在这里`\d{10}`代表匹配10个数字。这里的`re.S`代表匹配换行在内的所有字符。 图5-1 包含电话号码的文件   第2步:使用`with open()`方法读取`1.txt`文件中的所有内容 ```python with open("1.txt","r",encoding="utf-8") as f: con = f.read() ```   第3步:使用`re.findall()`方法在文本文件中匹配出所有的电话号码 ```python phones = re.findall(regx,con) print(phones) ```   使用`findall()`方法将所有匹配的电话号码放在一个列表中进行返回,通过for循环遍历得到每一个电话号码。 ```python for phone in phones: print(phone) ```   运行结果如下 ```python 15639031421 17586921425 16912356246 15863494256 19145623458 17456238964 13465756235 ``` # 5.2 正则表达式高级语法 ## 5.2.1 反向引用   正则表达式的反向引用是一种非常强大的特性,它可以让我们引用已经匹配的文本,以便在匹配后的模式中再次使用。本小节将会讲解反向引用的语法和使用方法,以及两个实际案例来演示如何使用反向引用解决实际问题。   反向引用语法:   在正则表达式中,反向引用使用“\”符号,后面跟一个数字,表示引用前面已经匹配的分组。例如,“\1”表示引用第一个分组,“\2”表示引用第二个分组,以此类推。   【示例5-9】验证重复的单词   有一个字符串包含一些重复的单词,想要验证这些单词是否是真正的重复。例如,字符串"hello world world"中的world就是重复单词。 ```python import re text = "hello world world" pattern = r"\b(\w+)\b\s+\1\b" match = re.search(pattern, text) if match: print("重复单词: ", match.group()) else: print("没有重复单词") ```   运行结果 ```python 重复单词: world world ```   这个正则表达式的含义是,`\b`表示单词的边界,`\w+`表示匹配一个或多个单词字符,`\s+`表示匹配一个或多个空白字符,`\1`表示反向引用第一个分组,也就是前面匹配的单词。这个正则表达式会匹配到第一个`world world`,因为它们是重复的单词。   【示例5-10】验证HTML标签   有一个包含HTML标签的字符串,想要验证这些标签是否正确嵌套。例如,`<div><p></p></div>`中的标签是正确嵌套的,而`<div><p></div></p>`中的标签则不是。下面是一个使用反向引用来验证`HTML`标签的正则表达式示例。 ```python import re html = "<div><p></p></div>" pattern = r"<(\w+)(\s*\w+=(\"[^\"]*\"|'[^']*'))*\s*>(.*?)<\/\1>" match = re.search(pattern, html) if match: print("HTML标签是正确嵌套的") else: print("HTML标签不是正确嵌套的") ```   运行结果 ```python HTML标签是正确嵌套的 ```   这个正则表达式的含义是,`<(\w+)`表示匹配一个以`<`开头,后面跟着一个或多个单词字符的标签;`(\s*\w+=(\"[^\"]*\"|'[^']*'))*`表示匹配一个或多个标签属性;`\s*>`表示匹配标签的结尾;`(.*?)`表示匹配任意字符,但是尽可能少的匹配;`<\/\1>`表示反向引用第一个分组,也就是前面匹配的标签名,加上`<\/`和`>`,表示匹配标签的结尾。这个正则表达式会匹配到`<div><p></p></div>`中的标签,因为它们是正确嵌套的。 ## 5.2.2 零宽度断言 ### 1.什么是零宽断言   有时候在使用正则表达式做匹配的时候,希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容,但又不想要前面或后面的这个特定的内容,这时候就需要零宽断言的帮助。所谓零宽断言,简单来说就是匹配一个位置,这个位置满足某个正则,但是不纳入匹配结果的,所以叫“零宽”,而且这个位置的前面或后面需要满足某种正则。   比如对于一个字符串:`finished going done doing`,我们希望匹配出其中的以ing结尾的单词,就可以使用零宽断言: ```python import re s = 'finished going done doing' p = re.compile(r'\b\w+(?=ing\b)') print([x + 'ing' for x in re.findall(p,s)]) # 结果是 ['going', 'doing'] ```   可以看出从中匹配出了`going`和`doing`两个单词,达到目的。   这里正则中使用的`(?=ing\b)`就是一种零宽断言,它匹配这样一个位置:这个位置有一个`ing`字符串,后面跟着一个`\b`符号,并且这个位置前面的字符串满足正则:`\b\w+`,于是匹配结果就是:`['go','do']`。 ### 2.不同的零宽断言   零宽断言分为下面四种:正预测先行断言、正回顾后发断言、负预测先行断言、负回顾后发断言,不同的断言匹配的位置不同。   上面四种零宽断言理解为:`正`指的是肯定预测,即某个位置满足某个正则匹配模式,`负`则指的是否定预测,即某个位置不要满足某个正则;`预测先行`则指的是`往后看`,`先往后走`的意思,即这个位置是出现在某一个字符串后面的,而与之相反的`回顾后发`则指的是相反的意思:`往前看`,即匹配的这个位置是出现在某个字符串的前面。 #### (1)正预测先行断言:(?=exp)   匹配一个位置(但结果不包含此位置)之前的文本内容,这个位置满足正则`exp`,例如:匹配出字符串`s`中以`ing`结尾的单词的前半部分。   【示例5-11】匹配出字符串s中以ing结尾的单词的前半部分 ```python import re s = "I'm singing while you're dancing." p = re.compile(r'\b\w+(?=ing\b)') result = re.findall(p,s) print(result) ```   运行结果 ```python ['sing', 'danc'] ``` #### (2)正回顾后发断言:(?<=exp)   匹配一个位置(但结果不包含此位置)之后的文本,这个位置满足正则`exp`,例如:匹配出字符串`s`中以`do`开头的单词的后半部分。   【示例5-12】匹配出字符串s中以do开头的单词的后半部分 ```python import re s = "doing done do todo" p = re.compile(r'(?<=\bdo)\w+\b') result = re.findall(p,s) print(result) ```   运行结果 ```python ['ing', 'ne'] ``` #### (3)负预测先行断言:(?!exp)   匹配一个位置(但结果不包含此位置)之前的文本,此位置不能满足正则`exp`,例如:匹配字符串中的所有空格,但是仅当空格后面不是句号、问号或感叹号时才匹配。   【示例5-13】匹配字符串中的所有空格 ```python import re text = "This is a test. Is it a good test? No! It's a bad test." pattern = r"\s(?![.?!])" matches = re.findall(pattern, text) print("匹配的空格数量:", len(matches)) print("匹配的空格:", [m for m in matches]) ```   运行结果 ```python 匹配的空格数量: 13 匹配的空格: [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] ```   上述正则表达式的含义是,`\s`表示匹配任意空白字符,`(?![.?!])`表示负向先行断言,匹配之后不是句号、问号或感叹号的字符,即不匹配出现在这三个标点符号之后的空格。这个正则表达式会匹配到字符串中的所有空格,但是不会匹配出现在句号、问号或感叹号之后的空格。 #### (4)负回顾后发断言:(?<!exp)   匹配一个位置(但结果不包含此位置)之后的文本,这个位置不能满足正则`exp`。例如:匹配字符串中的所有数字,但是仅当数字前面不是字母或下划线时才匹配。   【示例5-14】匹配字符串中的所有数字 ```python import re text = "The price of the product is ¥29.99, and the discount is 10%." pattern = r"(?<![a-zA-Z_])\d+" matches = re.findall(pattern, text) print("匹配的数字数量:", len(matches)) print("匹配的数字:", matches) ```   运行结果 ```python 匹配的数字数量: 3 匹配的数字: ['29', '99', '10'] ```   这个正则表达式的含义是,`\d+`表示匹配任意多个数字,`(?<![a-zA-Z_])`表示负回顾后发断言,匹配之前不是字母或下划线的字符,即不匹配出现在字母或下划线之前的数字。这个正则表达式会匹配到字符串中的所有数字,但是不会匹配出现在字母或下划线之前的数字。 #### (5)正向零宽断言的结合使用   正向零宽度断言可以用来匹配符合某个模式的文本,但是要求这些文本必须出现在另一个模式的前面。匹配一个字符串中所有被括号包含的数字,但是仅当这些数字出现在某个特定的单词前面时才匹配。   【示例5-15】匹配字符串中的所有被括号包含的数字 ```python import re text = "The price of the product (SKU: 1234) is ¥29.99, and the discount is 10% (valid until tomorrow)." pattern = r"(?<=SKU: )\d+|(?<=discount is )\d+" matches = re.findall(pattern, text) print("匹配的数字数量:", len(matches)) print("匹配的数字:", matches) ```   运行结果 ```python 匹配的数字数量: 2 匹配的数字: ['1234', '10'] ```   上述正则表达式的含义是,`\d+`表示匹配任意多个数字,`(?<=SKU: )`和`(?<=discount is )`分别表示正向零宽度断言,匹配之前是`SKU: `或`discount is `的字符,即只匹配出现在这两个短语前面的数字。 这个正则表达式会匹配到字符串中被括号包含的数字,并且这些数字都出现在特定的单词前面。 #### (6)负向零宽断言的结合使用   负向零宽度断言用于匹配符合某个模式的文本,但是要求这些文本必须不出现在另一个模式的前面。下面是一个结合使用负向零宽度断言的示例,匹配一个字符串中所有以`.com`结尾的`URL`,但是要求这些`URL`不出现在括号中。   【示例5-16】匹配一个字符串中所有以.com结尾的URL ```python import re text = "Please visit our website at https://www.example.com (not https://www.example.com /blog)." pattern = r"(?<!\()\bhttps?://[\w.-]+\.com\b(?!/|\))" matches = re.findall(pattern, text) print("匹配的URL:", matches) ```   运行结果 ```python 匹配的URL: ['https://www.example.com'] ```   这个正则表达式的含义是,`\bhttps?://[\w.-]+\.com\b`表示匹配以`.com`结尾的`URL`,`(?<!\()`和`(?!/|\))`分别表示负向零宽度断言,匹配之前不是(且之后不是`/`或)的字符,即不匹配出现在括号中的`URL`。这个正则表达式会匹配到字符串中所有以`.com`结尾的`URL`,并且这些`URL`都不出现在括号中。 ## 5.2.3贪婪和非贪婪匹配   正则表达式匹配默认是贪婪的,表示尽可能多的去匹配字符。非贪婪匹配则是尽可能少的匹配字符,可以通过在量词后面加上`?`来实现。下面是两个实际示例,一个是`贪婪匹配`,一个是`非贪婪匹配`。   在`python`的正则模式中,贪婪匹配采用`.*`来实现,非贪婪匹配采用`.*?`来实现。 ### 1.贪婪匹配   【示例5-17】匹配一个字符串中的HTML标签 ```python import re text = "<div>hello</div>" pattern = r"<.*>" match = re.search(pattern, text) print(match.group()) ```   运行结果 ```python <div>hello</div> ```   上述正则表达式的含义是,`<.*>`表示匹配任意多个字符,直到遇到第一个`>`符号,这个过程中尽可能多地匹配字符。在这个示例中,正则表达式会匹配整个字符串`<div>hello</div>`,而不是分别匹配`<div>`和`</div>`。 ### 2.非贪婪匹配   【示例5-18】匹配一个字符串中最短的HTML标签 ```python import re text = "<div>hello</div><p>world</p>" pattern = r"<.*?>" matches = re.findall(pattern, text) print(matches) ```   运行结果 ```python ['<div>', '</div>', '<p>', '</p>'] ```   上述正则表达式的含义是,`<.*?>`表示匹配任意多个字符,直到遇到第一个`>`符号,这个过程中尽可能少地匹配字符。在这个示例中,正则表达式会匹配`<div>`和`</p>`这两个最短的`HTML`标签。 # 5.3 正则表达式的性能优化(选讲)   当正则表达式处理的字符串数量非常多时,一些配置比较低的电脑在进行匹配的时候,速度会非常慢影响工作效率。为了提高正则表达式的匹配速度,可以使用一些性能优化技巧。下面以两个实际示例介绍如何通过优化正则表达式来提高性能。 ## 5.3.1 避免回溯   正则表达式中的回溯是指在匹配失败后,重新回到前面的位置尝试其他的匹配方式。回溯可能会导致正则表达式的性能急剧下降。为了避免回溯,可以使用非捕获组来限制回溯的范围。   【示例5-19】匹配一个字符串中所有的IPv4地址 ```python import re text = "192.168.0.1, 10.0.0.1, 172.16.0.1, 192.168.1.1, 192.168.0.100" pattern = r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b" matches = re.findall(pattern, text) print(matches) ```   运行结果 ```python ['192.168.0.1', '10.0.0.1', '172.16.0.1', '192.168.1.1', '192.168.0.100'] ```   这个正则表达式使用非捕获组`(?:...)`来限制回溯的范围,只匹配符合条件的`IP`地址,而不是在每个位置都尝试匹配`IP`地址。这种方式可以显著提高正则表达式的性能。 ## 5.3.2 使用正则表达式预编译   正则表达式在每次匹配时都需要进行解析和编译,这个过程可能会占用大量的时间。为了提高正则表达式的性能,可以使用正则表达式预编译的功能,将正则表达式编译成一个可复用的对象。   【示例5-20】匹配一个字符串中的所有单词 ```python import re text = "hello world, how are you today?" pattern = r"\b\w+\b" regex = re.compile(pattern) # 创建正则对象进行预编译操作 matches = regex.findall(text) print(matches) ```   运行结果 ```python ['hello', 'world', 'how', 'are', 'you', 'today'] ```   这个示例中,先使用`re.compile()`方法将正则表达式编译成一个正则对象`regex`,然后使用`regex.findall()`方法来匹配字符串。这种方式可以避免在每次匹配时都重新编译正则表达式,从而提高性能。 # 5.4 常见问题及其解决方案和技巧   当使用正则表达式时,遇到的常见问题及其解决方案和技巧如下: ### 1.匹配一个字符串中的所有数字   使用正则表达式`\d+`来匹配一个字符串中的所有数字。其中,`\d`表示匹配任意一个数字字符,`+`表示匹配一个或多个连续的数字字符。 ### 2.匹配一个字符串中的所有字母   使用正则表达式`[a-zA-Z]+`来匹配一个字符串中的所有字母。其中,`[a-zA-Z]`表示匹配任意一个大小写字母,`+`表示匹配一个或多个连续的字母字符。 ### 3.匹配一个字符串中的所有空格   使用正则表达式`\s+`来匹配一个字符串中的所有空格。其中,`\s`表示匹配任意一个空白字符(包括空格、制表符、换行符等),`+`表示匹配一个或多个连续的空白字符。 ### 4.匹配一个字符串中的所有非空格字符   使用正则表达式`\S+`来匹配一个字符串中的所有非空格字符。其中,`\S`表示匹配任意一个非空白字符,`+`表示匹配一个或多个连续的非空白字符。 ### 5.匹配一个字符串中的所有邮箱地址   使用正则表达式`\b[A-Za-z0-9.%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}\b`来匹配一个字符串中的所有邮箱地址。其中,`\b`表示单词边界,`[A-Za-z0-9.%+-]`表示匹配任意一个字母、数字或指定的特殊字符,`+`表示匹配一个或多个连续的字符,`@`表示匹配一个`@`符号,`[A-Za-z0-9.-]+`表示匹配任意一个字母、数字、点号或横杆,`.[A-Z|a-z]{2,}`表示匹配任意一个顶级域名(如 `.com`、`.cn`等),`{2,}`表示至少匹配两个字符。 ### 6.匹配一个字符串中的所有链接地址   使用正则表达式`\bhttp[s]?://(?:[a-zA-Z]|[0-9]|[$-@.&+]|[!*,]|(?:%[0-9a-fA-F][0-9a-fA-F]))+`来匹配一个字符串中的所有链接地址。其中,`\b`表示单词边界,`http[s]?`表示匹配`http`或`https`协议,`(?:...)`表示非捕获组,`[a-zA-Z]|[0-9]|[$-@.&+]|[!*,]|(?:%[0-9a-fA-F][0-9a-fA-F])`表示匹配链接地址中可能包含的字符,包括字母、数字、`$`、`_`、`-`、`@`、`.&+`、`!*`、`%xx`(其中`xx`表示两个16进制数)等。最后的`+`表示匹配一个或多个连续的字符。 ### 7.匹配一个字符串中的所有手机号码   使用正则表达式`\b1[3-9]\d{9}\b`来匹配一个字符串中的所有手机号码。其中,`\b`表示单词边界,`1`表示匹配数字`1`,`[3-9]`表示匹配数字`3-9`中的任意一个,`\d`表示匹配任意一个数字字符,`{9}`表示匹配连续的`9`个数字字符。 ### 8.匹配一个字符串中的所有IP地址   使用正则表达式`\b(?:[0-9]{1,3}.){3}[0-9]{1,3}\b`来匹配一个字符串中的所有`IP`地址。其中,`\b`表示单词边界,`(?:...)`表示非捕获组,`[0-9]{1,3}`. 表示匹配任意一个`1-3`位数字字符和一个点号,`{3}`表示匹配连续的3段数字和点号,`[0-9]{1,3}`表示匹配最后一段数字。   以上是一些常见的正则表达式问题及其解决方案和技巧。在使用正则表达式时,应根据具体需求选择适当的正则表达式,并注意正则表达式中特殊字符的含义和使用规则。 # 5.5 本章小结   本章主要介绍了`Python`中正则表达式的概念、普通字符和特殊字符的正则表达式语法,以及`re`模块的使用,在`re`使过程中要注意区分`re.search()`、`re.match()`、`re.findall()`的用法。还学习了正则表达式的高级用法:反向引用,零宽度断言和贪婪匹配。 `re.match()`:从字符串开头匹配一个模式。 `re.search()`:在字符串中搜索匹配一个。 `re.findall()`:在字符串中搜索匹配所有的模式,并返回一个列表。 `re.sub()`:在字符串中搜索匹配的模式,并将其替换为指定的字符串。 `re.split()`:使用指定的模式分割字符串,并返回一个列表。   在实际使用正则表达式时,需要注意以下几点:   (1) 正则表达式语法是一门独立的语言,需要学习和掌握其语法规则和特殊字符的含义。   (2) 正则表达式可以非常强大,但也容易变得非常复杂。在编写正则表达式时,需要尽可能简单明了,避免过度使用特殊字符和复杂的模式。   (3) 在使用正则表达式时,需要测试和验证其效果。可以使用在线正则表达式测试工具或编写简单的`Python`脚本进行测试。   总之,正则表达式是一项非常重要的技能,可以大大提高文本处理的效率和精度。在`Python`中,使用`re`模块可以轻松实现正则表达式的功能。对于需要频繁处理文本数据的开发者来说,学习和掌握正则表达式是非常有价值的。 # 5.6 思考练习 ## 一、选择题 1. 正则表达式对应的`python`模块是( )。 A. os模块 B. sys模块 C. json模块 D. re模块 2. Python正则表达式中,( )可以表示0个或者1个。 A. "+" B. "^" C. "?" D. "*" 3. 正则匹配模式`\d{3}`可以匹配的内容是( )。 A. "abc" B. "3ab" C. "135" D. "a35" 4. 正则匹配模式`"[a|b+]AB"`可以匹配到的内容是( )。 A. "abAB" B. "bbbbAB" C. "aAB" D. "ab" 5. 针对字符串`s1="a1b22c33"`,`re.match("[a-z]\d{1,3}",s1)`匹配的结果是( )。 A. "a1" B. "a122" C. "122333" D. "abc" ## 二、简答题 1. 能够完全匹配字符串`back`和`back-end`的正则表达式是什么。 2. 创建正则对象的函数是什么。 3. 匹配26个小写字母单词中的任意一个的正则匹配规则是什么。 4. 如果需要匹配连续3个数字,正则匹配规则如何写? 5. `print(re.match("^[a-zA-Z]+$", "abcDEFG000"))`的结果是什么。 ## 三、编程题 1. 字符串s中保存了论语中的一句话,请编程统计s中汉字的个数和标点符号的个数。 `s="学而时习之,不亦说乎?有朋自远方来,不亦说乎?人不知而不愠,不亦君子乎?"`。 2. 提取用户输入数据中的数值 (数值包括正负数 还包括整数和小数在内) 并求和。例如:`-3.14good87nice19bye` =====> `-3.14 + 87 + 19 = 102.86`。 3. 匹配`0-100`之间的数字(包含0和100)。 4. 编写程序,用户输入一段英文,输出这段英文中所有长度为3的单词。 5. 有一段英文,其中有单词连续出现了2次,编写程序检查重复的单词并只保留一个,文本内容为`Welocme to to China!`,程序输出为`Welocme to China!`。
张泽楠
2024年6月23日 14:19
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码