01. 正则表达式基本介绍

1.1 正则表达式介绍

  • 正则表达式是一种特殊的字符串,是一款常用于校验、筛选、检索、替换字符串内容的工具。
  • 正则表达式有其自己独特的语法,以及处理引擎,在所有编码语言中几乎都存在。
    • 正则表达式的执行效率比Python低。
    • 因此能用Python完成的,就用Python完成,Python完成不了的再用正则表达式。
  • 在线练习网站:https://regex101.com/
  • 教程网站:https://github.com/ziishaned/learn-regex/blob/master/translations/README-cn.md

    1.2 正则表达式应用场景

  • 注册时用户名、密码、手机号码、邮箱等信息的格式校验。

  • 爬虫,从网页文本中提取指定格式的信息。

    02. 正则表达式常用语法

    2.1 基本匹配

  • 正则表达式其实就是在执行搜索时的格式,它由一些字母和数字组成。例如最简单的正则表达式123,就表示一个规则:由数字1开始,接着是2,再接着是3。

  • 正则表达式是大小写敏感的,比如abc不会匹配AbC

    “the” == The fat cat sat on the mat.

2.2 元字符

  • 正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。

    2.2.1 .点运算符

  • “.”可以匹配任意单个字符,但不匹配换行符\n。

    “.ar” == The car parked in the garage.

2.2.2 []字符集与^非

  • 字符集也称字符类。方括号用来指定一个字符集,如[1bm]找的是1或者b或者m。
  • 像这种ASCII码连续的,可以使用“-”来连接,如:[0-9][a-z][A-Z][a-zA-Z0-9]
  • 在方括号中使用连字符来指定字符集的范围,这种方式下在方括号中的字符集不关心顺序。

    “[a-d][Hh]e” == There are some words: ahe bHe cHe dHe.

  • 方括号中的点”.”就表示句号。

    “ar.” == A garage is a good place to park a car. “ar[.]” == A garage is a good place to park a car.

  • 在方括号内以"^"开头的字符是否定字符,表示这个字符集是否定的,比如[^N]BA就不可能匹配”NBA”

    “[c]ar” == The car parked in the garage. “[^c]ar” == The car parked in the garage.

2.2.3 +、*、?重复次数

  • 后面跟着元字符+、*、?的,用来指定匹配子模式的次数,这些元字符在不同的情况下有着不同意思。
  • 号:匹配在号之前的字符出现大于等于0次(即可以不出现,也可以出现多次)。

    • 例如表达式a匹配0或更多个以a开头的字符。abc就是匹配以a开头,c结尾,中间有n个b的字符。

      “[a-z]“ == The car parked in the garage. “abc” == ac adc abc adddddddddc abbbbbbc

    • 字符和.字符搭配可以匹配所有的字符.

      “.“ == *Apache Hadoop 3.1.4.

    • *字符和表示匹配空格的符号\s连用,可以表示任意个空格,包括0个。

    • 如\sword\s可以匹配0个或多个空格开头且以0个或多个空格结尾的word字符串。

      “\scat\s“ == The fat cat sat on the concatenation.

  • +号:匹配+号之前的字符出现 >=1 次,即至少一次。

    • 例如表达式c.+t 匹配以首字母c开头以t结尾,中间跟着至少一个字符的字符串。

      “c.+t” == ct cat cabct mat “c.+t” == The fat cat sat on the mat.

  • ?号:标记在符号前面的字符为可选,即出现 0 或 1 次(但不可出现多次)。

    • 例如,表达式[T]?he匹配字符串he和The。

      “[T]he” == The car is parked in the garage. “[T]?he” == The car is parked in the garage.

2.2.4 {}重复出现的次数

  • {}是一个量词,用来限定一个或一组字符可以重复出现的次数。
  • {m} 表示前面的符号连续出现m次,例如:匹配3位数的数字。

    “[0-9]{3}” == The number was 9.99977 but we rounded it off to 10.0.

  • {m,} 表示前面的符号连续出现最少m次,例如:匹配最少两位的数字。

    “[0-9]{2,}” == The number was 9.99977 but we rounded it off to 10.0.

  • {m,n} 表示前面的符号连续出现最少m次,最多n次,例如:匹配2~3位的数字。

    “[0-9]{2,3}” == The number was 9.99977 but we rounded it off to 10.0.

2.2.5 (…)特征标群(分组匹配)

  • 类似于数学中的小括号,(..)中的内容会被看成一个整体,多个整体之间用|分割。

    “(ab)“ == abbbbc ababc ababababccccab abbbbbbbbbcccddd “(c|g|p)ar” == The car is parked in the garage. “(he.{2}|ni.{2})” == headnicegoodbyehello*heatnight

2.2.6 |或运算符

  • 或运算符就表示或,用作判断条件
  • 例如(T|t)he表示The或the,(T|t)he|car表示(T|t)he或car

    “(N|C)BA|basketball” == NBA and CBA are basketball association.

  • 注意:或运算是会短路的,因此需要把大范围写在前面,小范围写在后面。

  • 具体效果在4.6.1中描述。

    2.2.7 \转码特殊字符

  • 反斜线”\”在表达式中用于转码紧跟其后的字符。

  • 换言之,如果想要匹配这些特殊字符则要在其前面加上反斜线\。
  • 可转码的字符:{、}、[、]、/、\、+、*、.、$、^、|、?

    “(f|c|m)at.?” == The fat cat sat on the mat. “{ | }“ == public void main(String[] args) { System.out.println(“Hello World!”); }

2.2.8 ^开头锚点与$结尾锚点

  • ^a:以a开头的字符串

    “^(c|p|g)ar” == car parked agegar “^(c|p|g)ar” == car parked agegar

  • a$:以a结尾的字符串

    “(c|p|g)ar$” = car parked agegar

2.3 简写字符集

  • . 除换行符\n外的所有字符。
  • \w 匹配所有数字字母下划线(包括英文字母以及其他文字的字母)。
  • \W 匹配所有非字母非数字非下划线的符号,等同于[^\w]
  • \d 匹配数字,等同于[0-9]
  • \D 匹配非数字,等同于[^\d]
  • \f 匹配一个换页符
  • \n 匹配一个换行符
  • \r 匹配一个回车符
  • \t 匹配一个制表符
  • \v 匹配一个垂直制表符
  • \p 匹配CR/LF(等同于\r\n),用来匹配DOS行终止符
  • \s 匹配所有空格字符,等同于[\t\n\f\r\p{Z} ]
  • \S 匹配所有非空格字符,等同于[^\s]
  • \b 匹配非连续符号的边界
  • \B 匹配非边界,即左右都有内容的连续字符

    03. Python中的正则表达式

    3.1 re模块概述

  • Python中用re库处理正则表达式,re是Python的自带的库,因此无需下载,直接导入即可。

    1. import re

    3.2 匹配操作

    3.2.1 compile()正则对象

  • 若要在Python中用正则表达式匹配内容,则第一步就是要生成正则表达式对象。

  • re.compile(正则表达式[, 匹配模式])可以根据正则表达式语法生成正则对象。 ```python import re

匹配以h开头,第二个字符是为\n的任意字符,第三个字符为a,没有限制结尾的内容。

pattern_obj = re.compile(r”^h.a”) # 正则中的所有字符串都是原生字符串,用r修饰。 print(pattern_obj) # re.compile(‘^h.a’)

  1. <a name="bO1Nw"></a>
  2. #### 3.2.2 match()校验匹配
  3. - `正则对象.match(原始字符串数据)`:会从头开始验证原始字符串数据是否匹配正则对象。
  4. - 如果正则对象的语法中没有限制内容的结尾,match()就会校验原始字符串数据是否以正则语法可匹配的内容开头。
  5. - 如果正则对象的语法中限制了内容的结尾,match()就会校验原始字符串数据的内容是否满足正则语法的格式。
  6. - 若能校验成功,就返回对应的匹配match对象,若校验失败,则返回None。
  7. - 示例一(没有限制内容的结尾):验证head、hand是否匹配4.2.1中的pattern_obj。
  8. ```python
  9. import re
  10. pattern_obj = re.compile(r"^h.a")
  11. res1 = pattern_obj.match("head")
  12. print(res1) # <re.Match object; span=(0, 3), match='hea'>,校验成功,返回对应的匹配match对象。
  13. res2 = pattern_obj.match("hand")
  14. print(res2) # None,校验失败,返回None
  15. # 因为match()本来就是从头开始匹配的,因此正则中不限定开头也是可以的。
  16. pattern_obj = re.compile(r"h.a")
  17. res1 = pattern_obj.match("head")
  18. res2 = pattern_obj.match("hand")
  19. # 打印发现效果都是一样的。
  20. print(res1, res2) # <re.Match object; span=(0, 3), match='hea'> None
  • 示例二(限制内容的结尾):验证hand、hands是否以h开头,以d结尾。 ```python import re

pattern_obj = re.compile(r”^h.*d$”) res1 = pattern_obj.match(“hand”) print(res1) # ,校验成功,返回对应的匹配match对象。

res2 = pattern_obj.match(“hands”) print(res2) # None,校验失败,返回None

  1. <a name="hI93m"></a>
  2. #### 3.2.3 fullmatch()校验匹配
  3. - `正则对象.fullmatch(原始字符串数据)`:等价于`match()`的第二种情况,即从头到尾进行验证。
  4. ```python
  5. import re
  6. pattern_obj = re.compile(r"h.*d") # 没有指定开头和结尾
  7. res1 = pattern_obj.fullmatch("hand")
  8. print(res1) # <re.Match object; span=(0, 4), match='hand'>,校验成功,返回对应的匹配match对象。
  9. res2 = pattern_obj.fullmatch("hands")
  10. print(res2) # None,校验失败,返回None

3.2.4 匹配实战(用户名是否合法)

  • 需求:输入一个用户名,判断用户名是否合法。要求用户名由6~12位的英文字母或数字组成,且不能由数字开头。
  • 正则分析:
    • 因为不能由数字开头,因此第一个字符只能是英文字母(包括大小写),故正则开头为:^[a-zA-Z]
    • 除了开头,其他字符由英文字母或数字组成,即[a-zA-Z0-9],且除去开头字符,剩余长度应该为5~11位,组合起来即为[a-zA-Z0-9]{5,11}
    • 最终的正则表达式为:^[a-zA-Z][a-zA-Z0-9]{5,11}
  • 代码实现: ```python import re

username = input(“username = “) pattern_obj = re.compile(r”^[a-zA-Z][a-zA-Z0-9]{5,11}”) if pattern_obj.fullmatch(username) is not None: print(f”{username=}合法”) else: print(f”{username=}不合法”)

  1. <a name="rHkJg"></a>
  2. ### 3.3 获取匹配对象数据
  3. <a name="Am3h6"></a>
  4. #### 3.3.1 match对象分析
  5. - match()/fullmatch()方法若校验成功,则会返回对应的匹配match对象,如:`<re.Match object; span=(0, 3), match='hea'>`
  6. - match对象的输出格式如下:
  7. - re.Match object:代表这是一个match对象。
  8. - span=(0, 3):代表匹配到的内容从原始字符串数据的0索引开始,到3索引结束(不包括3)。
  9. - match='hea':代表匹配上的内容为hea。
  10. <a name="iPRkc"></a>
  11. #### 3.3.2 span()获取起止索引
  12. - `match对象.span()`:可以获取匹配内容在原始字符串中的起止索引,即match对象中的span,且获取到的内容是元组类型的数据。
  13. ```python
  14. import re
  15. pattern_obj = re.compile(r"h.a")
  16. res = pattern_obj.match("head")
  17. # 在获取内容前一定要判断是否为None,否则可能会出现AttributeError异常。
  18. if res is not None:
  19. print(res.span(), type(res.span())) # (0, 3) <class 'tuple'>

3.3.3 group()分组获取匹配文本

  • 默认情况下,match对象.group()可以获取匹配上的内容,即match对象中的match。 ```python import re

pattern_obj = re.compile(r”h.a”) res = pattern_obj.match(“head”) if res is not None: print(res.group()) # hea

  1. - group()函数详解:
  2. - group这个单词是分组的意思,之所以可以获取匹配的文本内容,是因为group()在默认情况下会将整个正则表达式看成一个大组。
  3. - 因为缺省情况下没有传入任何限制参数,因此group()函数会获取整个正则匹配的内容,也就实现了它的默认功能,即获取正则匹配的文本。
  4. - 除此之外,group()还可以传入一个int类型的小组索引和str类型的小组名来获取指定小组的内容。
  5. - int类型分组:在整个正则表达式中,group()会将一个特征标群分为一个小组,然后从左到右进行从1~n的排序。
  6. - 现有一个邮箱正则表达式:`^[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)$`
  7. - 邮箱的格式:用户名@邮箱公司名称.后缀。
  8. - 用户名:要求由数字、英文字母、下划线组成,长度为6~18位,故正则为:`[a-zA-Z0-9_]{6,18}`
  9. - 邮箱公司:常见的有qqsina163126,故正则为:`(qq|sina|163|126)`
  10. - 后缀地址:常见的有.com、.net、.cn,故正则为:`\.(com|net|cn)`
  11. - 要求输入一个邮箱地址,若地址合法,则输出这个邮箱地址以及公司、后缀地址。
  12. ```python
  13. import re
  14. # 输入邮箱,并判断邮箱是否合法。
  15. email = input("请输入一个邮箱地址:")
  16. pattern_obj = re.compile(r"^[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)$")
  17. res = pattern_obj.match(email)
  18. # 若邮箱合法,则输出这个邮箱地址以及公司、后缀地址;否则提示邮箱不合法。
  19. if res is not None:
  20. print(f"邮箱地址:{res.group()}")
  21. print(f"邮箱公司:{res.group(1)}") # 从左往右数第一个特征标群,即:(qq|sina|163|126)
  22. print(f"后缀地址:{res.group(2)}") # 从左往右数第二个特征标群,即:(com|net|cn)
  23. else:
  24. print("邮箱不合法!")
  25. """
  26. 运行结果:
  27. 请输入一个邮箱地址:8120398@qq.com
  28. 邮箱地址:8120398@qq.com
  29. 邮箱公司:qq
  30. 后缀地址:com
  31. """
  • 以str类型分组:可以先在正则表达式中给特征标群命名,然后根据标群名获取指定小组的数据。
    • 如用(?P<标群名>正则)的形式给邮箱公司和后缀地址命名:^[a-zA-Z0-9_]{6,18}@(?P<company>qq|sina|163|126)\.(?P<end_address>com|net|cn)$
    • 然后用group(标群名)的形式即可获取指定的数据。 ```python import re

email = input(“请输入一个邮箱地址:”) patternobj = re.compile(r”^[a-zA-Z0-9]{6,18}@(?Pqq|sina|163|126).(?Pcom|net|cn)$”) res = pattern_obj.match(email)

if res is not None: print(f”邮箱地址:{res.group()}”) print(f”邮箱公司:{res.group(‘company’)}”) print(f”后缀地址:{res.group(‘end_address’)}”) else: print(“邮箱不合法!”)

“”” 运行结果: 请输入一个邮箱地址:8120398@qq.com 邮箱地址:8120398@qq.com 邮箱公司:qq 后缀地址:com “””

  1. <a name="QW9SA"></a>
  2. ### 3.4 查找操作
  3. <a name="bOcGw"></a>
  4. #### 3.4.1 search()查找首个
  5. - `正则对象.search(原始字符串数据)`:会在字符串数据中搜索满足正则对象的内容,如果有满足的内容,则返回查找到的第一个满足要求的Match对象,如果没有满足的内容,则返回None。
  6. - 示例:下例程序中a1、b2、c3、d4都是满足正则要求的字符,但search()只获取到了第一个a1的Match对象。
  7. ```python
  8. import re
  9. pattern_obj = re.compile("[a-z][0-9]")
  10. print(pattern_obj.search("a1b2c3d4")) # <re.Match object; span=(0, 2), match='a1'>

3.4.2 findall()查找所有

  • 正则对象.findall(原始字符串数据):会在字符串数据中搜索满足正则对象的内容,如果有满足的内容,则返回存有查找到的所有满足要求的字符内容的列表,如果没有满足的内容,则返回空列表[]
  • 示例一:4.3.1中的示例程序用findall()得到的就是一个存有匹配结果字符的列表。 ```python import re

pattern_obj = re.compile(“[a-z][0-9]”) print(pattern_obj.findall(“a1b2c3d4”)) # [‘a1’, ‘b2’, ‘c3’, ‘d4’]

  1. - 示例二:查找字符串`s = "headnicegoodbyehelloheatnight"`中所有以"he"开头,长度为4的字符串。
  2. ```python
  3. import re
  4. s = "headnicegoodbyehelloheatnight"
  5. pattern_obj = re.compile(r"he.{2}")
  6. res = pattern_obj.findall(s)
  7. print(res) # ['head', 'hell', 'heat']

3.4.3 findall()的弊端

  • 引入案例:
    • 现有一段文本数据:data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
    • 要求提取出所有邮箱地址。
  • 代码实现: ```python import re

data = “明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com” patternobj = re.compile(r”[a-zA-Z0-9]{6,18}@(qq|sina|163|126).(com|net|cn)”) res = pattern_obj.findall(data) print(res) # [(‘163’, ‘com’), (‘126’, ‘net’), (‘qq’, ‘com’)]

  1. - 可以很明显的发现,打印的结果与运行结果是不一样的,这是由于findall()自身的弊端所导致的。
  2. - findall()的弊端:当正则表达式中有特征标群进行分组时,结果中只展示特征标群的内容,而不是整个正则表达式的内容。
  3. <a name="i4Lrx"></a>
  4. #### 3.4.4 finditer()查找所有
  5. - finditer()实际上很好的解决了findall()的弊端问题。
  6. - `正则对象.finditer(原始字符串数据)`:会在字符串数据中搜索满足正则对象的内容。若有满足的内容,则把匹配内容的Match对象放到迭代器中。
  7. - 示例:用finditer()实现4.4.3中的程序。
  8. ```python
  9. import re
  10. data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
  11. pattern_obj = re.compile(r"[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)")
  12. res_iter = pattern_obj.finditer(data)
  13. print(res_iter) # <callable_iterator object at 0x000002367F0884F0>,结果是一个迭代器
  14. # 遍历迭代器,获取match
  15. for match_obj in res_iter:
  16. print(match_obj, match_obj.group())
  17. """
  18. 运行结果:
  19. <re.Match object; span=(4, 19), match='1234566@163.com'> 1234566@163.com
  20. <re.Match object; span=(26, 44), match='agdha34345@126.net'> agdha34345@126.net
  21. <re.Match object; span=(49, 63), match='3452658@qq.com'> 3452658@qq.com
  22. """
  23. # 纯邮箱列表版
  24. import re
  25. data = "明明邮箱1234566@163.com 清清邮箱地址agdha34345@126.net 欢欢邮箱3452658@qq.com"
  26. pattern_obj = re.compile(r"[a-zA-Z0-9_]{6,18}@(qq|sina|163|126)\.(com|net|cn)")
  27. email_list = []
  28. for match_obj in pattern_obj.finditer(data):
  29. email_list.append(match_obj.group())
  30. print(email_list) # ['1234566@163.com', 'agdha34345@126.net', '3452658@qq.com']

3.5 贪婪匹配

3.5.1 贪婪匹配概述

  • *(≥0)、+(≥1)、{m,}(≥m)这三个正则符号匹配的次数都是没有上限的,因此被称为贪婪匹配。
  • 如有一段HTML文本:<div>asdasdau1289u,,12jasdm.mkk</div><div></div>,要求匹配出文中的第一个div标签对。 ```python import re

s_str = r”

asdasdau1289u,,12jasdm.mkk
“ pattern_obj = re.compile(r”^
.*
$”) # 以
开头,以
结尾,中间是任意长度的任意字符。 d_str = pattern_obj.search(s_str).group() print(d_str) #
asdasdau1289u,,12jasdm.mkk

  1. - 可以发现,`<div>asdasdau1289u,,12jasdm.mkk</div><div></div>`并不是我们想要的结果(`<div>asdasdau1289u,,12jasdm.mkk</div>`)。但是`<div>asdasdau1289u,,12jasdm.mkk</div><div></div>`却也是满足正则`^<div>.*</div>$`的要求的,即以<div>开头,以</div>结尾,中间是任意长度的任意字符。
  2. - 这种情况就是贪婪匹配的一种体现,即虽然匹配到第一个`</div>`时已经满足匹配要求了,但是加上后面的`<div></div>`还是满足要求的,所以正则表达式就会尽可能多的匹配下去,就出现了上述的情况。
  3. <a name="GJkOQ"></a>
  4. #### 3.5.2 惰性匹配
  5. - 所谓惰性匹配就是要让正则表达式在第一次满足要求后就停下来,用4.3.1中的例子就是在匹配到第一个`</div>`后就停下,不再继续匹配后面的`</div>`
  6. - 可以用`?`来实现惰性匹配,即:`*?``+?``{m,}?`
  7. - 示例:完善4.3.1中的程序。
  8. ```python
  9. import re
  10. s_str = r"<div>asdasdau1289u,,12jasdm.mkk</div><div></div>"
  11. pattern_obj = re.compile(r"<div>.*?</div>") # 用?限制贪婪匹配,且不限制开头和结尾。
  12. d_str = pattern_obj.search(s_str).group()
  13. print(d_str) # <div>asdasdau1289u,,12jasdm.mkk</div>

3.6 特定内容提取

3.6.1 提取数字

  • 匹配数字的正则:[-]?([1-9][0-9]+|[0-9])(\.[0-9]+)?
    • 正负号:正数没有负号,负数有符号,因此符号要出现0次或1次,正则:[-]?
    • 整数正则:[-]?([1-9][0-9]+|[0-9])
      • 一位数的情况:一位数就是0~9,正则:[-]?[0-9]
      • 多位数的情况:首位不能为0,只能是1~9;之后的位数可以是0~9,且至少出现一次,正则:[-]?[1-9][0-9]+
    • 小数正则:([-]?[1-9][0-9]+|[-]?[0-9])\.[0-9]+
      • 小数正则实际上就是在整数正则的基础上加上小数部分。
      • 小数部分包括一个小数点:\.,以及小数的数字(0~9的数字,出现至少一次):[0-9]+
    • 整数与小数整合:[-]?([1-9][0-9]+|[0-9])(\.[0-9]+)?
      • 小数部分正则出现一次即为小数。
      • 小数部分正则出现零次即为整数。
  • 示例:从数据data = "-23.8good$^28cnmxs3.14¥%*&123"中提取所有数字信息。 ```python import re

data = “-23.8good$^28cnmxs3.14¥%*&123” pattern_obj = re.compile(r”[-]?([1-9][0-9]+|[0-9])(.[0-9]+)?”) res_iter = pattern_obj.finditer(data) res_list = [eval(match.group()) for match in res_iter] print(res_list) # [-23.8, 28, 3.14, 123]

  1. - 2.2.6短路描述:若`([1-9][0-9]+|[0-9])`变成了`([0-9]|[1-9][0-9]+)`,则所有多位数数据匹配到一位数就短路了,不会匹配到后面的多位数正则。
  2. ```python
  3. import re
  4. data = "-23.8good$^28cnmxs3.14¥%*&123"
  5. # 大范围在前,小范围在后
  6. pattern_obj1 = re.compile(r"([1-9][0-9]+|[0-9])")
  7. res1 = pattern_obj1.findall(data)
  8. print(res1) # ['23', '8', '28', '3', '14', '123']
  9. # 小范围在前,大范围在后
  10. pattern_obj2 = re.compile(r"([0-9]|[1-9][0-9]+)")
  11. res2 = pattern_obj2.findall(data)
  12. print(res2) # ['2', '3', '8', '2', '8', '3', '1', '4', '1', '2', '3']
  • 所以|与操作一定要大的范围写在前面,小的范围写在后面。

    3.6.2 边界匹配

  • 边界匹配使用\b、\B这两个简写字符集完成:

    • \b:匹配非连续符号的边界。
    • \B:匹配非边界,即左右都有内容的连续字符。
  • 示例1:用\b找出文本中所有以w开头的单词。
    • 首先,一句英文句子中的单词前后是不连续的,不连续就是有边界,即\bword\b。
    • 接着,w开头的单词第一个字符一定是w,而单词是由一个或多个字母组成的内容。
    • 即w开头的单词的正则为:\bw.+?\b(?是用来限制贪婪匹配的) ```python import re

s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’

pattern_obj = re.compile(r”\bw.+?\b”) res = pattern_obj.findall(s) print(res) # [‘was’, ‘world’, ‘world’]

  1. - 示例2:用找出包含en的单词。
  2. - 首先,一句英文句子中的单词前后是不连续的,不连续就是有边界,即\bword\b
  3. - 接着,所谓包含en,就是单词中有en这个字符串,有以下三种情况:
  4. - en开头,即en前面没有任何字母,en后面有任意个字母。
  5. - en在中间,即en前后都有任意个字母。
  6. - en结尾,即en前面有任意个字母,en后面没有任何字母。
  7. - 组合起来就是`\w*?en\w*?`
  8. - 将两点结合在一起,得到最后的正则:`\b\w*?en\w*?\b`
  9. ```python
  10. import re
  11. s = '''my parents pay special attention to my education. since I was three years old, they start
  12. to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i
  13. listen to a lot of english songs and i am so eager to learn more about the world. mastering
  14. english is the key to know more about the world.'''
  15. pattern_obj = re.compile(r"\b\w*?en\w*?\b")
  16. res = pattern_obj.findall(s)
  17. print(res) # ['parents', 'attention', 'english', 'listen', 'english', 'english']
  • 示例3:用\B找出单词中间有en的单词。
    • en在中间还指en前后都有任意个字母,且en与这些字母是连续的(\B)。
    • 故可以将示例2中的正则改写成:\b\w*?\Ben\B\w*?\b ```python import re

s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’

pattern_obj = re.compile(r”\b\w?\Ben\B\w?\b”) res = pattern_obj.findall(s) print(res) # [‘parents’, ‘attention’]

  1. <a name="WXFTz"></a>
  2. ### 3.7 正则的切割与替换
  3. <a name="VMhi2"></a>
  4. #### 3.7.1 split()分割字符串
  5. - 使用字符串的split()函数分割字符串时只能指定一种分隔符。
  6. - 如用字符串的split()函数提取文本中所有单词时,一般只能指定分割符为空格,此时一句话中最后一个单词就会连着句号`.`一起被切出来。
  7. ```python
  8. s = '''my parents pay special attention to my education. since I was three years old, they start
  9. to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i
  10. listen to a lot of english songs and i am so eager to learn more about the world. mastering
  11. english is the key to know more about the world.'''
  12. words = s.split()
  13. print(words) # ['my', 'parents', 'pay', 'special', 'attention', 'to', 'my', 'education.', 'since', 'I', 'was', 'three', 'years', 'old,', 'they', 'start', 'to', 'hire', 'a', 'tutor', 'to', 'teach', 'me', 'english.', 'as', 'the', 'saying', 'that', 'interest', 'is', 'the', 'best', 'teacher,', 'indeed,', 'i', 'listen', 'to', 'a', 'lot', 'of', 'english', 'songs', 'and', 'i', 'am', 'so', 'eager', 'to', 'learn', 'more', 'about', 'the', 'world.', 'mastering', 'english', 'is', 'the', 'key', 'to', 'know', 'more', 'about', 'the', 'world.']
  14. print([w for w in words if w.endswith(".")]) # ['education.', 'english.', 'world.', 'world.']
  • 正则对象.split(原始字符串数据):会将正则对象匹配到的内容作为分隔符,将原始字符串数据进行切割。
  • 这种方式来提取文本中所有单词时,可以将空格、逗号、句号、感叹号、问号等全部设置为分隔符,只要正则匹配上的全部切割掉。 ```python import re

s = ‘’’my parents pay special attention to my education. since I was three years old, they start to hire a tutor to teach me english. as the saying that interest is the best teacher, indeed, i listen to a lot of english songs and i am so eager to learn more about the world. mastering english is the key to know more about the world.’’’

pattern_obj = re.compile(r”\s|[,.!?]”) res = pattern_obj.split(s) print(res) # [‘my’, ‘parents’, ‘pay’, ‘special’, ‘attention’, ‘to’, ‘my’, ‘education’, ‘’, ‘since’, ‘I’, ‘was’, ‘three’, ‘years’, ‘old’, ‘’, ‘they’, ‘start’, ‘to’, ‘hire’, ‘a’, ‘tutor’, ‘to’, ‘teach’, ‘me’, ‘english’, ‘’, ‘as’, ‘the’, ‘saying’, ‘that’, ‘interest’, ‘is’, ‘the’, ‘best’, ‘teacher’, ‘’, ‘indeed’, ‘’, ‘i’, ‘listen’, ‘to’, ‘a’, ‘lot’, ‘of’, ‘english’, ‘songs’, ‘and’, ‘i’, ‘am’, ‘so’, ‘eager’, ‘to’, ‘learn’, ‘more’, ‘about’, ‘the’, ‘world’, ‘’, ‘mastering’, ‘english’, ‘is’, ‘the’, ‘key’, ‘to’, ‘know’, ‘more’, ‘about’, ‘the’, ‘world’, ‘’]

从结果中可以看出存在一些空字符串,需要过滤以下

res_end = [w for w in res if w != “”] print(res_end) # [‘my’, ‘parents’, ‘pay’, ‘special’, ‘attention’, ‘to’, ‘my’, ‘education’, ‘since’, ‘I’, ‘was’, ‘three’, ‘years’, ‘old’, ‘they’, ‘start’, ‘to’, ‘hire’, ‘a’, ‘tutor’, ‘to’, ‘teach’, ‘me’, ‘english’, ‘as’, ‘the’, ‘saying’, ‘that’, ‘interest’, ‘is’, ‘the’, ‘best’, ‘teacher’, ‘indeed’, ‘i’, ‘listen’, ‘to’, ‘a’, ‘lot’, ‘of’, ‘english’, ‘songs’, ‘and’, ‘i’, ‘am’, ‘so’, ‘eager’, ‘to’, ‘learn’, ‘more’, ‘about’, ‘the’, ‘world’, ‘mastering’, ‘english’, ‘is’, ‘the’, ‘key’, ‘to’, ‘know’, ‘more’, ‘about’, ‘the’, ‘world’]

  1. <a name="R1Ufm"></a>
  2. #### 3.7.2 sub()替换目标子字符串
  3. - 字符串中也有替换操作,但字符串中的替换操作只能替换指定内容的目标字符串,不能替换指定格式的目标字符串。
  4. ```python
  5. s = "你好110193287"
  6. print(s.replace("1", "*")) # 你好**0*93287
  • 正则对象.sub(新字符串, 原始字符串数据):根据正则对象在原始字符串数据中匹配数据,若匹配成功,则将匹配到的所有子字符串替换成新字符串。
  • 示例:将字符串s = "你好110193287Hello110193287"中所有的数字字符替换成*。 ```python import re

s = “你好110193287Hello110193287” pattern_obj = re.compile(r”\d”) res = pattern_obj.sub(““, s) print(res) # 你好**Hello*

  1. - sub()函数中的新字符串可以是一个固定的字符串,也可以是一个函数,在函数中接收正则对象匹配到的Match对象,并设置转换规则,其返回值就是要替换的内容。
  2. - 示例:将字符串`s = "你好110193287"`中所有的数字替换成其对应的中文大写形式。
  3. ```python
  4. import re
  5. def replace(match_obj):
  6. """
  7. :param match_obj: 正则对象匹配到的Match对象。
  8. :return: 要替换的内容。
  9. """
  10. transfer_dic = {
  11. "1": "壹",
  12. "2": "贰",
  13. "3": "参",
  14. "4": "肆",
  15. "5": "伍",
  16. "6": "陆",
  17. "7": "柒",
  18. "8": "捌",
  19. "9": "玖",
  20. "0": "零"
  21. } # 转换规则,键正则匹配到的内容,值为要替换的目标内容。
  22. return transfer_dic[match_obj.group()] # 获取Match对象的内容,获取其需要转换的目标内容并返回。
  23. s = "你好110193287"
  24. pattern_obj = re.compile(r"\d") # 匹配所有数字的正则对象
  25. res = pattern_obj.sub(replace, s) # 传入的是转换函数与原始字符串数据,而非调用转换函数得到的结果。
  26. print(res) # 你好壹壹零壹玖参贰捌柒

3.8 匹配模式

  • 在3.2.1中有提到,一个完整的compile函数写法为:re.compile(正则表达式[, 匹配模式])
  • 其中的匹配模式中一些常用的匹配模式如下:
    • re.I:进行匹配时忽略大小写。 ```python import re

pattern_obj_1 = re.compile(r”hel”) res1 = pattern_obj_1.match(“Hello”) print(res1) # None,说明正常情况下正则匹配是区分大小写的。

pattern_obj_2 = re.compile(r”hel”, re.I) res2 = pattern_obj_2.match(“Hello”) print(res2) #

  1. - `re.M`:多行匹配,影响的是多行文本中每一行以什么开头、以什么结尾的内容。
  2. - 正常情况下多行文本是没有办法定位到每一行的开头和结尾的。
  3. - 若非要匹配每一行的开头和结尾,就要将匹配模式设置为`re.M`
  4. ```python
  5. import re
  6. s = '''hello nice to meet you
  7. happy birthday to you
  8. good night
  9. i am fine
  10. hi my name is hanmeimei
  11. '''
  12. pattern_obj_1 = re.compile(r"^h.+")
  13. res1 = pattern_obj_1.findall(s)
  14. print(res1) # ['hello nice to meet you']。
  15. # 没有匹配到happy birthday to you和hi my name is hanmeimei,说明这种情况下只能匹配一行。
  16. pattern_obj_2 = re.compile(r"^h.+", re.M)
  17. res2 = pattern_obj_2.findall(s)
  18. print(res2) # ['hello nice to meet you', 'happy birthday to you', 'hi my name is hanmeimei']
  • re.S:让.匹配\n
    • 在2.2.1中有提到,.可以匹配任意单个字符,但不匹配换行符\n
    • 若要让.匹配\n,则需要将匹配模式设置为re.S。 ```python import re

s = ‘

abcd97NDJ#$%^\ndfghjk
‘ pattern_obj_1 = re.compile(r”
.*?
“) res1 = pattern_obj_1.search(s) print(res1) # None,正常情况下.是不匹配\n的,因此pattern_obj_1匹配到
abcd97NDJ#$%^就匹配不上了,故搜索失败。

pattern_obj_2 = re.compile(r”

.*?
“, re.S) res2 = pattern_obj_2.search(s) print(res2) #

  1. <a name="lHRUl"></a>
  2. ### 3.9 一道面试题
  3. - 题目:问下列程序的运行结果。
  4. ```python
  5. import re
  6. print(re.sub(".*", "+", "asd asd dj"))
  7. print(re.sub(".+", "+", "asd asd dj"))
  • 答案:

    1. ++
    2. +
  • 分析:+号匹配的是出现大于等于一次的字符,故.+就是将一整个字符串当成一个整体,故第二个print只输出一个+;号匹配的是出现零次或多次的字符,`.`会先匹配整个字符串,替换成一个+号,接着它还会匹配一个空字符,也替换成+号,故第一个print有两个+。