正则表达式详解

jefxff 66,447 2023-08-16

一、概念

正则表达式(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")

数量词是一个匹配次数的概念,用于指定前一个字符、字符组或子表达式可以重复出现的次数范围,以此来实现对字符串长度范围的匹配。

四、边界匹配

  1. ^ - 匹配字符串的开始
re.match("^Hello", "Hello world") # 可以匹配
re.match("^Hello", "Hi Hello") # 无法匹配,要求字符串必须以Hello开头,这里是以Hi开头所以无法匹配

^的作用就是要求匹配的内容必须出现在字符串的最开始。

  1. $ - 匹配字符串的结束
re.match("world$", "Hello world") # 可以匹配 
re.match("world$", "Hello world!") # 无法匹配,要求字符串必须以world结尾,这里多了一个!

$的作用就是要求匹配的内容必须出现在字符串的最末尾。

  1. \b - 匹配单词边界
re.match(r"\bworld", "Hello world") # 可以匹配,world前面有空格,是单词边界
re.match(r"\bworld", "Helloworld") # 无法匹配,world前面没有空格,不是单词边界

\b匹配的是两个单词之间的分隔,通常是空格。

  1. \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\d{4})
(?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>&nbsp;<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>&nbsp;<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.Ire.IGNORECASE: 不区分大小写的匹配,使匹配对大小写不敏感。

  • re.Lre.LOCALE: 根据所使用的本地语言环境通过\w、\W、\b、\B、\s、\S 实现匹配。

  • re.Mre.MULTILINE: ^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串本身的起始和结尾。

  • re.Sre.DOTALL: “.”(点号)通常匹配除了\n(换行符)之外的所有单个字符;该标记表示“.”(点号)能够匹配全部字符。

  • re.Xre.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')

# python # 正则表达式 # py