一、概念
正则表达式(Regular Expression)又称为正规表示法、正规表示式、正则表达式、规则表达式、常规表示法。
它是一种由字符和特殊符号组成的字符串模式,用来描述文本的格式规则,比如验证输入的数据是否符合邮箱或手机号的格式。
- 正则表达式通常被用来在文本中查找匹配某个模式的文本内容,例如在日志文件中查找包含某个关键字的日志项。
- 它匹配的是符合正则表达式规则的所有字符串,而不是某个特定字符串。
二、元字符
正则表达式中的元字符(Metacharacter)代表特定的语义,比如匹配多个字符或者表示字符串的边界。
常用的元字符包括:
字符 | 功能 | 示例 |
---|---|---|
. | 匹配任意1个字符(除了\n) | re.match('.','a') |
[] | 匹配[]中列举的字符,表示 “或” | re.match('[hH]',"Hello python") |
\d | (digit)匹配数字,即0~9相当于[0-9] | re.match("[0-9]","7Hello python") |
\D | 匹配非数字,即不是数字,与\d相反相当于 [^0-9] | re.match('\\D', 'a7') |
\s | 匹配空白,即空格、tab键相当于[\t\n\r\f\v] | re.match('\\s', ' ') |
\S | 匹配非空白,与\s相反 | re.match('\\S', 'a ') |
\w | 匹配字母数字下划线,即a-z、A-Z、0-9、_ 相当于[a-zA-Z0-9] | re.match('\\w', '_') |
\W | 匹配非单词的字符,与\w相反 | re.match('\\W', ' ') |
例如 . 可以匹配任意一个字符(除了换行符),[…] 用来表示匹配方括号中列举的任意字符,表示或的关系。
三、数量词
在正则表达式中,数量词(Quantifier)用来指定匹配次数。
常用的数量词包括:
量词 | 描述 | 示例 |
---|---|---|
* | 重复0次或多次 | re.match("a*", "aaa") |
+ | 重复1次或多次 | re.match("a+", "aa") |
? | 重复0次或1次 | re.match("ab?", "a") |
重复n次 | re.match("a{3}", "aaa") |
|
重复n到m次(指定范围) | re.match("a{1,3}", "aaa") |
数量词是一个匹配次数的概念,用于指定前一个字符、字符组或子表达式可以重复出现的次数范围,以此来实现对字符串长度范围的匹配。
四、边界匹配
- ^ - 匹配字符串的开始
re.match("^Hello", "Hello world") # 可以匹配
re.match("^Hello", "Hi Hello") # 无法匹配,要求字符串必须以Hello开头,这里是以Hi开头所以无法匹配
^的作用就是要求匹配的内容必须出现在字符串的最开始。
- $ - 匹配字符串的结束
re.match("world$", "Hello world") # 可以匹配
re.match("world$", "Hello world!") # 无法匹配,要求字符串必须以world结尾,这里多了一个!
$的作用就是要求匹配的内容必须出现在字符串的最末尾。
- \b - 匹配单词边界
re.match(r"\bworld", "Hello world") # 可以匹配,world前面有空格,是单词边界
re.match(r"\bworld", "Helloworld") # 无法匹配,world前面没有空格,不是单词边界
\b匹配的是两个单词之间的分隔,通常是空格。
- \B - 匹配非单词边界
re.match(r"\Bworld", "Hello world") # 无法匹配,world前有空格,是单词边界
re.match(r"\Bworld", "Helloworld") # 可以匹配,world前没有空格,是非单词边界
\B匹配的是单词内部,不包含空格等分隔符。
总结一下,^ $ 限定匹配字符串的两端,\b \B 限定匹配内容出现的位置是单词边界还是非单词边界。这可以避免正则表达式匹配到不需要的内容。
五、分组和引用
字符 | 功能 | 示例 |
---|---|---|
() | 将括号中的表达式作为一个组 | (ab) 将ab组合在一起匹配 |
| | 匹配左右任意一个表达式 | re.match("[1-9]?\d|100","95") 匹配0-100 |
\num | 引用序号为num的分组匹配到的字符串 | re.match(r"(a)(b)(c)\2\1","abcabc") |
(?P |
对分组起一个别名name | (?P |
(?P=name) | 引用别名为name的组匹配到的字符串 | (?P=id) |
示例:
import re
# \num 引用组
p = r"(a)(b)(c)\2\1"
print(re.match(p, "abcabc").group()) # abcabc
# 命名引用组
p = r"(?P<id>\d{4})-(?P<name>\w+)"
m = re.match(p, "1234-张三")
print(m.group('id')) # 1234
print(m.group('name')) # 张三
六、python 的re模块
在Python中,我们使用re模块来使用正则表达式。以下是该模块的一些主要函数和方法:
-
compile(pattern, flags=0)
: 使用任何可选的标记来编译正则表达式的模式,然后返回一个正则表达式对象。import re pattern = re.compile(r'\d+') match = pattern.match('hello123') if match: print(match.group()) # 输出:123
-
match(pattern, string, flags=0)
: 尝试使用带有可选的标记的正则表达式的模式来匹配字符串。如果匹配成功,就返回匹配对象;如果失败,就返回None。import re pattern = re.compile(r'\d+') match = pattern.match('hello 123') if match: print(match.group()) # 输出:123
-
search(pattern, string, flags=0)
: 使用可选标记搜索字符串中第一次出现的正则表达式模式。如果匹配成功,则返回匹配对象;如果失败,则返回None。import re # 示例1 pattern = re.compile(r'\d+') search = pattern.search('hello 123 world 456') if search: print(search.group()) # 输出:123 # 示例2 ret = re.search(r"\d+","阅读次数为 9999") s = "itcast" re.search(r"^itcast$",s) #表示的是search的s字符串必须是以i开头,并且以t结尾
-
findall(pattern, string [,flags])
: 查找字符串中所有(非重复)出现的正则表达式模式,并返回一个匹配列表。import re # 示例1 pattern = re.compile(r'\d+') string = 'hello 123 world 456' matches = pattern.findall(string) print(matches) # 输出:['123', '456'] # 示例2 ret = re.findall(r"\d+","python=9999, c=7890, c++=1234")
-
finditer(pattern, string [,flags])
: 与findall()函数相同,但返回的不是一个列表,而是一个迭代器。对于每一次匹配,迭代器都返回一个匹配对象。import re pattern = re.compile(r'\d+') string = 'hello 123 world 456' for match in pattern.finditer(string): print(match.group()) # 输出:123 和 456
-
split(pattern, string, max=0)
: 根据正则表达式的模式分隔符,split函数将字符串分割为列表,然后返回成功匹配的列表,分隔最多操作max次(默认分割所有匹配成功的位置)。import re # 示例1 pattern = re.compile(r'\d+') string = 'hello 123 world 456' split_string = pattern.split(string) print(split_string) # 输出:['hello ', ' world ', ''] # 示例2 s = "itcast:php,python,cpp-java" r = re.split(r":|,|-",s) print(r) # 输出 ['itcast', 'php', 'python', 'cpp', 'java']
-
re.sub() sub 将匹配到的数据进行替换
# 示例1:将匹配到的阅读次数加1 ret = re.sub(r"\d+",'998', "python=997") # 示例2:将匹配到的阅读次数加1 import re def add(temp): strNum = temp.group() num = int(strNum) + 1 return str(num) ret = re.sub(r"\d+", add, "python=997") # 示例3: import re s = """ <div> <p>岗位职责:</p> <p>完成推荐算法、数据统计、接⼝、后台等服务器端相关⼯作</p> <p><br></p> <p>必备要求:</p> <p>良好的⾃我驱动⼒和职业素养,⼯作积极主动、结果导向</p> <p> <br></p> <p>技术要求:</p> <p>1、⼀年以上 Python 开发经验,掌握⾯向对象分析和设计,了解设计模式</p> <p>2、掌握HTTP协议,熟悉MVC、MVVM等概念以及相关WEB开发框架</p> <p>3、掌握关系数据库开发设计,掌握 SQL,熟练使⽤ MySQL/PostgreSQL 中的⼀种<br></p> <p>4、掌握NoSQL、MQ,熟练使⽤对应技术解决⽅案</p> <p>5、熟悉 Javascript/CSS/HTML5,JQuery、React、Vue.js</p> <p> <br></p> <p>加分项:</p> <p>⼤数据,数理统计,机器学习,sklearn,⾼性能,⼤并发。</p> </div> """ # s表示的是前端网页上的一段代码,通过使用re.sub方法来替换掉<>尖括号 r = re.sub(r"</?\w+>","",s) print(r)
以下是正则表达式常用的匹配对象方法:
-
group(num=0)
: 返回整个匹配对象,或者编号为num的特定子组。 -
groups(default=None)
: 返回一个包含所有匹配子组(子字符串)的元组(如果没有成功匹配,则返回一个空元组)。 -
groupdict(default=None)
: 返回一个包含所有匹配的命名子组的字典,所有的子组名称作为字典的键(如果没有成功匹配,则返回一个空字典)。import re # 定义一个正则表达式 pattern = re.compile(r'(\d+)-(\w+)') # 定义一个待匹配的字符串 string = '12345-hello' # 进行匹配 match = pattern.match(string) # 使用 group 方法获取整个匹配对象 print(match.group()) # 输出:12345-hello # 使用 group 方法获取特定编号的子组,这里获取的是编号为1的子组(即数字部分) print(match.group(1)) # 输出:12345 # 使用 group 方法获取特定编号的子组,这里获取的是编号为2的子组(即字母部分) print(match.group(2)) # 输出:hello # 使用 groups 方法获取所有匹配的子组,返回一个元组 print(match.groups()) # 输出:('12345', 'hello') # 使用 groupdict 方法获取所有匹配的命名子组,返回一个字典 print(match.groupdict()) # 输出:{'first': '12345', 'second': 'hello'}
以下是正则表达式常用的模块属性:
-
re.I
或re.IGNORECASE
: 不区分大小写的匹配,使匹配对大小写不敏感。 -
re.L
或re.LOCALE
: 根据所使用的本地语言环境通过\w、\W、\b、\B、\s、\S 实现匹配。 -
re.M
或re.MULTILINE
: ^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾。 -
re.S
或re.DOTALL
: “.”(点号)通常匹配除了\n(换行符)之外的所有单个字符;该标记表示“.”(点号)能够匹配全部字符。 -
re.X
或re.VERBOSE
: 通过反斜线转义,否则所有空格加上#(以及在该行中所有后续文字)都被忽略,除非在一个字符类中或者允许注释并且提高可读性。 -
(?:...)
: 通过使用该符号,可以对部分正则表达式进行分组,但是并不会保存该分组用于后续的检索或者应用。当不想保存今后永远不会使用的多余匹配时,这个符号就非常有用。import re # 使用 re.I 属性进行不区分大小写的匹配 pattern = re.compile(r'.', re.I) match = pattern.match('Hello World') if match: print(match.group()) # 输出:Hello World # 使用 re.L 属性根据本地语言环境进行匹配 pattern = re.compile(r'\d', re.L) match = pattern.match('五一二三') if match: print(match.group()) # 输出:五 # 使用 re.M 属性进行多行匹配 pattern = re.compile(r'^Hello.*World$', re.M) match = pattern.match('Hello\nWorld') if match: print(match.group()) # 输出:Hello # World # 使用 re.S 属性进行包括换行符的匹配 pattern = re.compile(r'.*World', re.S) match = pattern.match('Hello\nWorld') if match: print(match.group()) # 输出:Hello # World # 使用 re.X 属性进行带有注释的匹配 pattern = re.compile(r'(?i)hello', re.X) match = pattern.match('Hello') if match: print(match.group()) # 输出:Hello
七、贪婪匹配
Python⾥数量词默认是贪婪的;正则表达式采用贪婪匹配模式,婪模式总是尝试匹配尽可能多的字符;
解决方法:可以在量词"*“,”?“,”+“,”{m,n}"后面加上 ? 实现非贪婪匹配。
# 默认贪婪匹配
re.match(".+(\d+)", "abc123").group(1) # 123
# 非贪婪匹配
re.match(".+?(\d+)", "abc123").group(1) # 123
import re
s = "This is a number 234-235-22-423"
r = re.match(r"(.+)(\d+-\d+-\d+-\d+-)",s)
r.group(2) # 输出 '4-235-22-423'
r.group() # 输出 ('This is a number 23','4-235-22-423')
解释:为什么会出现23被列在元组的第一个元素,而不是第二个元素电话号码中
原因:python默认是贪婪的,解释器在遇到正则式(.+)时,会一直到满足下一个规则 \d+ 时才截止,.+ 表示的表示匹配除 \n 换行符的所有元素,所以会一直从 T 开始匹配到 3 才截止,因为 234当中的 4 是满足 \d+ 的最小情况,所以会出现上面的情况。
r = re.match(r"(.+?)(\d+-\d+-\d+-\d+-)",s)
r.group() # 输出 ('This is a number','234-235-22-423')