今天终于搞清楚了正则表达式

1. 什么是正则表达式?

  • 维基百科

    正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

2. 正则表达式规则

模式 描述
\ 转义字符
^ 匹配一行字符串的开头
$ 匹配输入字符串的结束位置
* 匹配前面的子表达式零次或多次,等价于{0,}
+ 匹配前面的子表达式一次或多次,等价于{1,}
? 匹配前面的子表达式零次或一次,等价于{0,1}
n是一个非负整数,匹配确定的n次
n是一个非负整数,至少匹配n次
m和n均为非负整数,其中n<=m,最少匹配n次最多匹配m次。注意在逗号和两个数之间不能有空格
? 当该字符紧跟在任何一个其他限制符(,+,?,{n},{n,},{n,m*})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
. 匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“`(.
(pattern) 匹配pattern并获取这一匹配
(?:pattern) 匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“`(
(?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“`Windows(?=95
(?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“`Windows(?!95
(?<=pattern) 反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“`(?<=95
(?<!pattern) 反向否定预查,与正向否定预查类拟,只是方向相反。例如“`(?<!95
x|y 匹配x或y
[xyz] 匹配所包含的任意一个字符
[^xyz] 匹配未包含的任意一个字符
[a-z] 匹配指定范围内的任意字符
[^a-z] 匹配任何不在指定范围内的任意字符
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如:o\b可以匹配"hello"中的o,不能匹配"double"中的o
\B 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符
\d 匹配一个数字字符,相当于[0-9]
\D 匹配一个非数字字符。等价于[^0-9]
\f 匹配一个换页符。等价于\x0c\cL
\n 匹配一个换行符,相当于匹配一个换行符。等价于\x0a\cJ
\r 匹配一个回车符,等价于\x0d\cM
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v]
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v]
\t 匹配一个制表符。等价于\x09\cI
\v 匹配一个垂直制表符。等价于\x0b\cK
\w 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]
\W 匹配任何非单词字符。等价于“[^A-Za-z0-9_]
\xn 匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。
\num 匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符
\n 标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值
\nm 标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm
\nml 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml
\un 匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)

3. Python中使用正则表达式

3.1 match方法

match 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None。

代码示例:

import re

string = "Hello world!"
result = re.match("^Hell", string)
print(result)
print(result.group())
print(result.span())

运行结果:

<_sre.SRE_Match object; span=(0, 4), match='Hell'>
Hell
(0, 4)

match方法中第一个参数传入正则表达式,第二个参数传入字符串,返回一个SRE_Match对象,group方法返回匹配到的字符串,span方法返回匹配到的字符串在原字符串中的位置范围。

  • 匹配目标

    刚刚我们用match获取到了匹配的字符串内容,那我们如何在匹配到的字符串中提取到一部分内容呢?

    假如我们想要在匹配的字符串中提取手机号或电子邮箱,我们可以使用()将要提取的内容包裹起来。被()包裹起来的部分表示了一个子表达式,每一个子表达式对应一个分组,我们可以使用group传入分组的索引来获取子表达式提取的内容。

    import re
    
    string = "Hello everyone, my mobile number is 12345678901"
    result = re.match("^Hello\s[a-z]*\,\s[a-z|\s]*(\d{11})", string)
    print(result)
    print(result.group(1))	# 注意,索引为0时为match方法匹配到的结果
    

    运行结果:

    <_sre.SRE_Match object; span=(0, 47), match='Hello everyone, my mobile number is 12345678901'>
    12345678901
    
  • 通用匹配

    如果说在字符串中,每个字符我们都用相应的表达式去匹配的话那不是很麻烦呢,比如说没出现一次空格就用\s去匹配,每出现一次逗号就用\,去匹配,这样我们的正则表达式写起来就比较麻烦了,这是我们就可以用一个万能的匹配表达式.*来进行匹配,其中.可以匹配除换行符外的任意字符,*可以不限次数匹配前边的子表达式。

    还是刚才的字符串,我们的正则表达式就可以简化成如下写法:

    import re
    
    string = "Hello everyone, my mobile number is 12345678901"
    result = re.match("^Hello.*(\d{11})$", string)
    print(result)
    print(result.group())
    

    运行结果:

    <_sre.SRE_Match object; span=(0, 47), match='Hello everyone, my mobile number is 12345678901'>
    Hello everyone, my mobile number is 12345678901
    
  • 贪婪匹配与非贪婪匹配

    当我们使用.*,来进行匹配的时候,有时候匹配到的结果可能并不是我们想要的结果

    import re
    
    string = "Hello everyone, my mobile number is 1234567890. How about you"
    result = re.match("^Hello.*(\d+).*you$", string)
    print(result)
    print(result.group())
    print(result.group(1))
    

    运行结果:

    <_sre.SRE_Match object; span=(0, 61), match='Hello everyone, my mobile number is 1234567890. H>
    Hello everyone, my mobile number is 1234567890. How about you
    0
    

    我们可以看到我们使用\d+去匹配数字,然而匹配到的却只是最后一个数字,这是什么问题呢?

    这就涉及到一个新的概念,贪婪匹配与非贪婪匹配,那么什么是贪婪匹配,什么又是非贪婪匹配呢?

    贪婪匹配就是会尽可能多的去匹配满足条件的字符,所以.*匹配尽可能多的字符就把\d+匹配一次或多次数字字符给抢了,只给\d+留了一个字符。

    非贪婪匹配就是尽可能少的去匹配字符,只需要把我们正则表达式中的.*修改为.*?,这时\d+就可以匹配到所有数字字符了。

    import re
    
    string = "Hello everyone, my mobile number is 1234567890. How about you"
    result = re.match("^Hello.*?(\d+).*you$", string)
    print(result)
    print(result.group())
    print(result.group(1))
    

    运行结果:

    <_sre.SRE_Match object; span=(0, 61), match='Hello everyone, my mobile number is 1234567890. H>
    Hello everyone, my mobile number is 1234567890. How about you
    1234567890
    
  • 修饰符

    修饰符是个什么东西呢?它又有什么用处呢?我们来看一个例子:

    import re
    
    string = """Hello everyone, my mobile number 
    is 1234567890. How about you"""
    result = re.match("^Hello.*?(\d+).*you$", string)
    print(result)
    print(result.group())
    print(result.group(1))
    

    运行结果:

    None
    Traceback (most recent call last):
      File "E:/my_code/test/test.py", line 7, in <module>
        print(result.group())
    AttributeError: 'NoneType' object has no attribute 'group'
    

    这个时候我们可以看到,字符串只是不在一行了,刚刚的表达式就匹配不到内容了,这是为什么呢?

    原来我们用到的.*?只是匹配除换行符外的所有字符,所以才导致我们匹配失败,那么我们就没有办法去匹配了吗?不是这样的,我只需要加一个修饰符就能就能愉快的匹配了。

    import re
    
    string = """Hello everyone, my mobile number 
    is 1234567890. How about you"""
    result = re.match("^Hello.*?(\d+).*you$", string, re.S)
    print(result)
    print(result.group())
    print(result.group(1))
    

    运行结果:

    <_sre.SRE_Match object; span=(0, 62), match='Hello everyone, my mobile number \nis 1234567890.>
    Hello everyone, my mobile number 
    is 1234567890. How about you
    1234567890
    

    除此之外,还有一些其他的修饰符供我们使用:

    修饰符 描述
    re.I 使匹配对大小写不敏感
    re.L 做本地化识别(locale-aware)匹配
    re.M 多行匹配,影响 ^ 和 $
    re.S 使匹配包括换行在内的所有字符
    re.U 根据 Unicode 字符集解析字符。这个标志影响 \w、\W、\b 和 \B
    re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解

    未完待续... ...

posted @ 2021-07-12 15:52  逐梦传奇  阅读(186)  评论(0编辑  收藏  举报