• [python]二十、python正则表达式详解


    目录

    1、什么是正则?

    1.1、正则表达式的优缺点

    2、re模块

    2.1、match和search

    2.1.1、match对象

    2.2、findall或者finditer

    2.3、sub

    2.4、编译正则re.compile("匹配正则")

    3、基本正则匹配

    3.1、区间匹配

    3.2、取反

    3.3、或匹配

    3.4、占位符

    3.5、常用快捷方式

    3.6、开始与结束

    3.6.1、标志位

    4、正则重复

    4.1、通配符

    4.1.1、"?"

    4.1.2、"+"

    4.1.3、"*"

    4.1.4、{n,m}

    4.2、贪婪和非贪婪模式

    5、正则分组

    5.1、分组向后引用(引用分组)

    5.2、findall对非捕获分组和捕获分组的反应

    6、零宽断言


    1、什么是正则?

    正则的目的

    • 数据挖掘
      • 从一大堆文本中找到一小堆文本时。如,从文本是寻找email, ip, telephone等
    • 验证
      • 使用正则确认获得的数据是否是期望值。如,email、用户名、IP地址是否合法等
    • 非必要时慎用正则,如果有更简单的方法匹配,可以不使用正则
    • 指定一个匹配规则,从而识别该规则是否在一个更大的文本字符串中。
    • 正则表达式可以识别匹配规则的文本是否存在
    • 还能将一个规则分解为一个或多个子规则,并展示每个子规则匹配的文本

    1.1、正则表达式的优缺点

    • 优点:提高工作效率、节省代码
    • 缺点:复杂,难于理解

    2、re模块

    re模块是一个标准库,无需安装

    官方文档:https://docs.python.org/3/library/re.html

    2.1、match和search

    • re.search
      • 查找匹配项
      • 接受一个正则表达式和字符串,并返回发现的第一个匹配。
      • 如果完全没有找到匹配,re.search返回None
    • re.match
      • 从字符串头查找匹配项
      • 接受一个正则表达式和字符串,从主串第一个字符开始匹配,并返回发现的第一个匹配。
      • 如果字符串开始不符合正则表达式,则匹配失败,re.match返回None
    1. >>> import re
    2. >>> result = re.search("sanchuang","hello world,this is sanchuang") # 前面写匹配字段,后面写要匹配的字符串
    3. >>> result
    4. <_sre.SRE_Match object; span=(20, 29), match='sanchuang'> # 这是一个match对象
    5. >>> result = re.search("sanchuang1","hello world,this is sanchuang") # 这里没有匹配到字符串
    6. >>> result
    7. >>> print(result)
    8. None
    9. >>> result = re.search("san.*$","hello world,this is sanchuang") # 匹配"san"开头的往后所有的字符串
    10. >>> result
    11. <_sre.SRE_Match object; span=(20, 29), match='sanchuang'>
    12. >>> result = re.match("san.*$","hello world,this is sanchuang")
    13. >>> result
    14. >>> result = re.match("hello","hello world,this is sanchuang")
    15. >>> result
    16. <_sre.SRE_Match object; span=(0, 5), match='hello'>
    17. # 如果匹配到了就会返回一个match对象;如果没有匹配上那就返回一个None
    18. """
    19. """
    20. # match只能从字符串开头查找,开始的部分没有,那就匹配不上。也就是说只匹配字符串的开始
    21. >>> result = re.search("sanchuang","hello world,this is sanchuang sanchuang") # 这个匹配是一个字符一个字符匹配的
    22. >>> result
    23. <_sre.SRE_Match object; span=(20, 29), match='sanchuang'>
    24. >>> dir(result)
    25. ['__class__', '__copy__', '__deepcopy__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'end', 'endpos', 'expand', 'group', 'groupdict', 'groups', 'lastgroup', 'lastindex', 'pos', 're', 'regs', 'span', 'start', 'string']
    26. >>> result.start()
    27. 20
    28. >>> result.end() # 匹配的是一个左闭右开区间
    29. 29
    30. >>> result.group() # 显示匹配的结果
    31. 'sanchuang'

    2.1.1、match对象

    如上代码所示:

    • match.group(default=0):返回匹配的字符串。
      • group是由于正则表达式可以分拆为多个只调出匹配子集的子组。
      • 0是默认参数,表示匹配的整个串,n 表示第n个分组
    • match.start()
      • start方法提供了原始字符串中匹配开始的索引
    • match.end()
      • end方法提供了原始字符串中匹配开始的索引
      • star()和end()组成的区间是左闭右开的区间

    2.2、findall或者finditer

    作用:找到多个匹配

    • re.findall
      • 查找并返回匹配的字符串,返回一个列表
    • re.finditer
      • 查找并返回匹配的字符串,返回一个迭代器
    1. """
    2. >>> msg = "i love pythonpython1python2"
    3. >>> re.findall("python",msg)
    4. ['python', 'python', 'python']
    5. >>> re.finditer("python",msg) # finditer出来的是一个迭代器
    6. >>> for i in re.finditer("python", msg):
    7. ... print(i)
    8. ...
    9. <_sre.SRE_Match object; span=(7, 13), match='python'>
    10. <_sre.SRE_Match object; span=(13, 19), match='python'>
    11. <_sre.SRE_Match object; span=(20, 26), match='python'>
    12. """

    2.3、sub

    re.sub('匹配正则' , '替换内容' , 'string') 

    • 将string中匹配的内容替换为新内容
    1. >>> msg = "i love python1 pythonyy python123 pythontt"
    2. # /d,表示[0-9]
    3. >>> re.sub("python\d","**",msg) # 第一参数表示你要匹配的字符串,第二参数表示你要替换的字符串,第三个参数表示你要进行替换操作的字符串
    4. 'i love ** pythonyy **23 pythontt'

    2.4、编译正则re.compile("匹配正则")

    编译正则的特点:

    • 复杂的正则可复用。
    • 使用编译正则更方便,省略了参数。
    • re模块缓存它即席编译的正则表达式,因此在大多数情况下,使用compile并没有很大 的性能优势
    1. >>> msg = "i love python1 pythonyy python123 pythontt"
    2. >>> reg = re.compile("python[0-9]")
    3. >>> reg.findall(msg)
    4. ['python1', 'python1']

    3、基本正则匹配

    最简单的正则表达式是那些仅包含简单的字母数字字符的表达式,复杂的正则可以实现强大的匹配

    3.1、区间匹配

    1. import re
    2. ret = re.findall("[Pp]y","Python3 pth ppython") # [Py]只占一个字符的位置
    3. print(ret)
    4. ret1 = re.findall("[Pp]yf","Python3 pth ppython") # 没有匹配上就是一个空列表
    5. print(ret1)
    6. # 按ASCILL编码排序,前面字符的编码一定要比后面字符的编码小
    7. ret2 = re.findall("[0-9a-z]y","Python3 pth ppython") # [Py]只占一个字符的位置
    8. print(ret2)
    9. #####result
    10. ['Py', 'py']
    11. []
    12. ['py']

    3.2、取反

    1. ret = re.findall("[^A-z]y","Python3 1yth ppython")
    2. print(ret)
    3. #######result
    4. ['1y']

    3.3、或匹配

    1. msg = 'xkjsdfjlaskdf lksdjf aslkfjdsfd'
    2. print(re.findall("sk|lk|as", msg))
    3. #####result
    4. ['as', 'lk', 'as', 'lk']

    3.4、占位符

    占位符,表示除了换行符以外的任意一个字符

    1. ret = re.findall("p.thon","Python python pgthon pthon p thon p\nthon")
    2. print(ret)
    3. #######result
    4. ['python', 'pgthon', 'p thon']

    3.5、常用快捷方式

    1. # \A 匹配字符串的开始
    2. # \b 词边界
    3. # \B 非词边界
    4. # \w 匹配单词字符,数字和中文是可以匹配单词字符
    5. # \W 匹配非单词字符
    6. # \s 匹配空白字符,如:换行符、制表符
    7. # \S 匹配非空白字符
    8. # \d 匹配数字
    9. # \D 匹配非数字
    1. import re
    2. # 正则表达式前面加r:抑制转义
    3. # 原样交给正则表达式引擎去匹配
    4. ret = re.findall(r'\bword', "word abcword 中word #word word123") #python写好之后交给正则表达式的独立引擎去去执行
    5. # abcword 会被认为是一个单词
    6. # 123word ,123是单词字符
    7. print(ret)
    8. ##### result
    9. ['word', 'word', 'word'] # 相应匹配的是word #word word123

    3.6、开始与结束

    # 匹配开始:^
    # 匹配结束:$
    
    1. msg = """Python
    2. python
    3. python123
    4. python345
    5. """ # 这里实际上是"Python\npython\npython123\npython345"
    6. ret = re.findall("^[Pp]ython",msg)
    7. print(ret)
    8. #####result
    9. ['Python']

    3.6.1、标志位

    放在findall的第三个参数这里

    • re.M 多行模式
    • re.I  忽略大小写
    • re.S  让 . 表示任意字符(包括换行符)
    1. import re
    2. msg = """Python
    3. python
    4. python123
    5. python345
    6. """ # 这里实际上是"Python\npython\npython123\npython345"
    7. ret = re.findall("^[Pp]ython",msg,re.M,)
    8. print(ret)
    9. ret = re.findall("^python",msg,re.M|re.I)
    10. print(ret)
    11. ret = re.findall("python.*",msg,re.I)
    12. print(ret)
    13. ret = re.findall("python.*",msg,re.S|re.I)
    14. print(ret)
    15. ####### result
    16. ['Python', 'python', 'python', 'python']
    17. ['Python', 'python', 'python', 'python']
    18. ['Python', 'python', 'python123', 'python345']
    19. ['Python\npython\npython123\npython345\n']

    4、正则重复

    4.1、通配符

    4.1.1、"?"

    1. # ? 匹配前一项0或者1次
    2. ret = re.findall("py?","py p python pyython")
    3. print(ret)
    4. ######result
    5. ['py', 'p', 'py', 'py']

    4.1.2、"+"

    1. # + 匹配前一项一次以上
    2. ret = re.findall("py+","py p python pyython")
    3. print(ret)
    4. ####result
    5. ['py', 'py', 'pyy']

    4.1.3、"*"

    1. # * 匹配前一项任意次
    2. ret = re.findall("py*","py p python pyython")
    3. print(ret)
    4. #### result
    5. ['py', 'p', 'py', 'pyy']

    4.1.4、{n,m}

    1. # {n,m} 匹配前一项n到m次,{n,} {,m}
    2. ret = re.findall("py{2,4}","pyyy py pyyyypyypy")
    3. print(ret)
    4. ###### result
    5. ['pyyy', 'pyyyy', 'pyy']

    思考:当我们匹配第一个字段和第三个字段的时候有"pyyy"和"pyyyy",那么为什么不能匹配到"pyy"就退出呢?

    那是因为python的默认模式是贪婪模式

    4.2、贪婪和非贪婪模式

    • 贪婪模式:匹配尽可能多的字符,贪婪量词为:"*"、"+"、”{n,m}“
    • 非贪婪模式:匹配到就退出。在贪婪量词后面加"?"
    1. import re
    2. msg = "<div>testdiv>bb<div>tedst2div>"
    3. ret = re.findall("<div>.*div>",msg) # 贪婪模式
    4. print(ret)
    5. ret = re.findall("<div>.*?div>",msg) # 非贪婪模式 + * {n,m} 后面接?都是非贪婪模式
    6. print(ret)
    7. ######result
    8. ['<div>testdiv>bb<div>tedst2div>']
    9. ['<div>testdiv>', '<div>tedst2div>']

    5、正则分组

    当使用分组时,除了可以获得整个匹配,还能够获得选择每一个单独组,使用 () 进行分组

    捕获分组和非捕获分组

    • 捕获分组(正则表达式)

      • 分组匹配上之后会把匹配上的数据放到内存中,并给定一个从1开始的索引

    1. ret = re.search(r"(\d{3})-(\d{3})-(\d{3})","abc123-456-789-aaa")
    2. print(ret.group())
    3. print(ret.group(1))
    4. print(ret.group(2))
    5. print(ret.group(3))
    6. ####result
    7. 123-456-789
    8. 123
    9. 456
    10. 789
    • 非捕获分组(?:正则表达式)

      • 只分组,不捕获,不会分配内存和下标去保存

    1. ret = re.search(r"(\d{3})-(?:\d{3})-(\d{3})","abc123-456-789-aaa")
    2. print(ret.group())
    3. print(ret.group(1))
    4. print(ret.group(2))
    5. #######result
    6. 123-456-789
    7. 123
    8. 789

    5.1、分组向后引用(引用分组)

    使用()分用,用\0, \1, \2引用 (\0表示匹配的整个串)

    1. msg = "aa bb aa bb"
    2. print(re.search(r"(\w+)\s(\w+)\s\1\s\2", msg)) # "\1"代表第一个分组
    3. msg = "aa bb aa cc"
    4. print(re.search(r"(\w+)\s(\w+)\s\1\s\2", msg)) # "\1"代表第一个分组
    5. ##### result
    6. object; span=(0, 11), match='aa bb aa bb'>
    7. None

    5.2、findall对非捕获分组和捕获分组的反应

    1. # 使用findall 有捕获分组,只会显示捕获分组的内容
    2. msg = "aa bb aa bb"
    3. print(re.findall(r"(\w+)\s(\w+)\s\1\s\2", msg)) # "\1"代表第一个分组
    4. msg = "comyy@xx.comcom123@qq.comaa@126.combbb@163.comcc@abc.com"
    5. # 把里面123 126 163邮箱捞出来
    6. # aaa@126.com bbb@163.com
    7. print(re.findall(r"(?:\.com)?(\w+@(?:123|126|163).com)",msg))
    8. #####result
    9. [('aa', 'bb')]
    10. ['aa@126.com', 'bbb@163.com']

    6、零宽断言

    作用:不占用匹配宽度,只用来确定位置

    1. s = "sc1 hello sc2 hello"
    2. # 匹配后面是sc2的hello
    3. print(re.findall(r"\w+ hello(?= sc2)",s))
    4. # 匹配后面不是sc2的hello
    5. print(re.findall(r"\w+ hello(?! sc2)",s))
    6. # 匹配前面是sc2 的hello
    7. print(re.findall(r"(?<=sc2 )hello",s))
    8. # 匹配前面不是sc2 的hello
    9. print(re.findall(r"(?,s))
    10. #####result
    11. ['sc1 hello']
    12. ['sc2 hello']
    13. ['hello']
    14. ['hello']
    1. #####截取出下面内容的IP地址
    2. msg = """
    3. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    5. inet 127.0.0.1/8 scope host lo
    6. valid_lft forever preferred_lft forever
    7. inet6 ::1/128 scope host
    8. valid_lft forever preferred_lft forever
    9. 2: ens33: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    10. link/ether 00:0c:29:16:6a:88 brd ff:ff:ff:ff:ff:ff
    11. inet 192.168.29.128/24 brd 192.168.29.255 scope global ens33
    12. valid_lft forever preferred_lft forever
    13. inet6 fe80::20c:29ff:fe16:6a88/64 scope link
    14. valid_lft forever preferred_lft forever
    15. 3: ens37: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    16. link/ether 00:0c:29:16:6a:92 brd ff:ff:ff:ff:ff:ff
    17. inet 192.168.29.71/24 brd 192.168.29.255 scope global ens37
    18. valid_lft forever preferred_lft forever
    19. inet6 fe80::20c:29ff:fe16:6a92/64 scope link
    20. valid_lft forever preferred_lft forever
    21. """
    22. print(re.findall(r"(?<=inet ).*(?=/24)",msg)[0])
    23. ##### result
    24. 192.168.29.128

  • 相关阅读:
    我的安卓AOSP开发使用到的教程汇总【安卓12】
    Linux命令(93)之head
    前端进击笔记第五节 JavaScript 如何实现继承?
    商品管理模块微服务demo
    NNDL 作业11:优化算法比较
    安卓BLE开发介绍
    JDK线程池ThreadPoolExecutor源码总结
    os_cfg.h、os_cpu.h和ucos_ii.h
    JAVA面试题——创建线程的三种方式
    主机安全加固
  • 原文地址:https://blog.csdn.net/m0_48638643/article/details/126286015