Python正则新解

作者: Rokkia | 来源:发表于2017-01-03 15:05 被阅读135次

    由于最近在使用BeautifulSoup时,发现文档中有这么一段:

    import re
    for tag in soup.find_all(re.compile("^b")): 
        print(tag.name)
    

    通过正则表达式来查询数据,虽然之前也了解过但是整体还是处于朦胧状态,昨天看了几个视频受益颇多,下面来分析一下.

    正则匹配一般匹配什么

    百度百科,有句话许多程序设计语言都支持利用正则表达式进行字符串操作,也就是说我们一般都是针对字符串来操作,字符串由字符构成,那么字符又有几种呢.

    字符种类

    1.a-zA-Z 英文字符
    2.0-9 数字字符
    3.[].,/? 等符号字符
    4.其他

    知道了字符的组成,我们在看看如何匹配

    表达式分类

    1.单个字符匹配(请在使用中添加re模块)

    . 匹配任意一个字符
    #通过.匹配一个英文字符
    In [**2**]: ma = re.match(r'.','h')
    
    #可以查看ma的类型
    In [**3**]: ma
    Out[**3**]: <_sre.SRE_Match at 0x103ddb098>
    
    In [**4**]: ma.group()
    Out[**4**]: 'h'
    
    #通过.匹配一个符号字符
    In [**5**]: ma = re.match(r'.','[')
    
    In [**6**]: ma.group()
    Out[**6**]: '['
    
    #通过.匹配一个数字字符
    In [**7**]: ma = re.match(r'.','7')
    
    In [**8**]: ma.group()
    Out[**8**]: '7'
    
    [...] 匹配一个字符集,这里是匹配的字符集,但是只能匹配一个字符

    1.当你只想匹配a/b/c时,你会发现使用.是不合适的,这时可以通过[abc]来实现

    #通过[abc]来匹配字符a
    In [**13**]: ma = re.match(r'[abc]','a')
    
    In [**14**]: ma.group()
    Out[**14**]: 'a'
    
    #通过[abc]来匹配f
    In [**15**]: ma = re.match(r'[abc]','f')
    
    #我们会发现ma没有group()方法
    In [**16**]: ma.group()
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-16-7c62fc675aee> in <module>()
    ----> 1 ma.group()
    
    AttributeError: 'NoneType' object has no attribute 'group'
    
    #这时ma为空,表示没有匹配到结果,很简单,因为[abc]中并不包含f
    In [**17**]: **print** (type(ma))
    <type 'NoneType'>
    

    2.在[]内开头添加^表示不等于
    如 [^123]

    几个特殊的字符集匹配,与上面一样,依旧是单个字符匹配

    \d 匹配数字字符,这里与C语言的%d一样 使用decimal的缩写来表示数字

    #使用\d匹配数字
    In [**20**]: ma = re.match(r'\d','2')
    
    In [**21**]: ma.group()
    Out[**21**]: '2'
    

    \D 匹配非数字字符

    #使用\D匹配数字字符
    In [**22**]: ma = re.match(r'\D','2')
    
    In [**23**]: ma
    #结果匹配不到结果
    In [**24**]: **print**(type(ma))
    <type 'NoneType'>
    #使用\D匹配非数字字符
    In [**25**]: ma = re.match(r'\D','+')
    #结果正确
    In [**26**]: ma.group()
    Out[**26**]: '+'
    

    \s 匹配空白字符

    #使用\s匹配空字符
    In [**27**]: ma = re.match(r'\s',' ')
    #结果正确
    In [**28**]: ma.group()
    Out[**28**]: ' '
    #使用\s匹配非空字符
    In [**29**]: ma = re.match(r'\s','w')
    #没有正确匹配
    In [**30**]: ma.group()
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-30-7c62fc675aee> in <module>()
    ----> 1 ma.group()
    
    AttributeError: 'NoneType' object has no attribute 'group'
    

    \S 匹配非空白字符

    #用\S匹配空字符
    In [**33**]: ma = re.match(r'\S','  ')
    #结果匹配失败
    In [**34**]: **print**(type(ma))
    <type 'NoneType'>
    #\S匹配非空字符
    In [**35**]: ma = re.match(r'\S','c')
    #成功匹配
    In [**36**]: ma.group()
    Out[**36**]: 'c'
    

    \w 匹配单词字符 也就是 [a-zA-Z0-9]这个字符集

    #用\w匹配单词字符
    In [**37**]: ma = re.match(r'\w','c')
    
    In [**38**]: ma.group()
    Out[**38**]: 'c'
    

    \W 匹配非单词字符 也就是除了数字英文数字之外的符号等字符

    #用\W匹配非单词字符
    In [**39**]: ma = re.match(r'\W','[')
    
    In [**40**]: ma.group()
    Out[**40**]: '['
    

    2.匹配多个字符的正则表达式

    *重复前一个字符0次或无限次

    来个简单例子:

    #当使用单个匹配时,只能匹配到第一个字符
    In [**41**]: ma = re.match(r'a','aaa')
    
    In [**42**]: ma.group()
    Out[**42**]: 'a'
    #使用多个匹配时,可以匹配到多个
    In [**43**]: ma = re.match(r'a*','aaa')
    
    In [**44**]: ma.group()
    Out[**44**]: 'aaa'
    #这里可以看出可以匹配0个a  于是返回一个空,可以与+对比看一下
    In [**45**]: ma = re.match(r'a*','   ')
    
    In [**46**]: ma.group()
    Out[**46**]: ''
    
    + 重复前一个字符至少1次或者无限次
    In [**47**]: ma = re.match(r'a+','aaaaa')
    #跟*一样可以匹配到 aaaaa
    In [**48**]: ma.group()
    Out[**48**]: 'aaaaa'
    #与*不同
    In [**49**]: ma = re.match(r'a+','  ')
    #+不支持重复0次
    In [**50**]: **print**(type(ma))
    <type 'NoneType'>
    
    ? 重复前一个字符0次或1次
    #不同于+/*  ?只会匹配返回1个字符
    In [**51**]: ma = re.match(r'a?','aaa')
    
    In [**52**]: ma.group()
    Out[**52**]: 'a'
    #如果为空,a? 也会匹配到0个字符
    In [**53**]: ma = re.match(r'a?',' ')
    
    In [**54**]: ma.group()
    Out[**54**]: ''
    
    {m} 重复前一个字符m次
    #匹配三个aaa 于是使用a{3},但是不会匹配到所有
    In [**55**]: ma = re.match(r'a{3}','aaaaa')
    
    In [**56**]: ma.group()
    Out[**56**]: 'aaa'
    
    #因为要匹配的'aa'不满足三个 于是返回为NoneType
    In [**57**]: ma = re.match(r'a{3}','aa')
    
    In [**58**]: ma.group()
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-58-7c62fc675aee> in <module>()
    ----> 1 ma.group()
    
    AttributeError: 'NoneType' object has no attribute 'group'
    
    {m,n}重复前一个字符m-n次
    #想要匹配3-5个a ,这里可以看到能匹配到3个
    In [**59**]: ma = re.match(r'a{3,5}','aaa')
    
    In [**60**]: ma.group()
    Out[**60**]: 'aaa'
    
    #想要匹配3-5个a ,这里可以看到并不能匹配到少于3个的字符串
    In [**61**]: ma = re.match(r'a{3,5}','aa')
    
    In [**62**]: ma.group()
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-62-7c62fc675aee> in <module>()
    ----> 1 ma.group()
    
    AttributeError: 'NoneType' object has no attribute 'group'
    
    #这里很有意思,明明有6个已经超越了5个为什么还能匹配到
    #因为这里只要包含3-5个  6个肯定包含5个所以能够匹配到
    In [**63**]: ma = re.match(r'a{3,5}','aaaaaa')
    
    In [**64**]: ma.group()
    Out[**64**]: 'aaaaa'
    
    *?,+?,??这三个都是将匹配模式变为非贪婪模式

    去查阅后才明白非贪婪模式,就是将最小匹配结果返回
    举两个例子

    #先让我们看一下贪婪模式下的结果
    In [**3**]: ma = re.match(r'a*','aaaa')
    #你会发现匹配结果将会是满足要求的最大结果,如果我变成10个a就会返回10个a
    In [**4**]: ma.group()
    Out[**4**]: 'aaaa'
    #然后看下非贪婪模式下的匹配结果
    In [**5**]: ma = re.match(r'a*?','aaaa')
    #None? 为什么是None ? 回忆一下*的作用,匹配0个或无限个前一个字符,所以在非贪婪模式下,将会按最小的0个来匹配,所以返回的是一个None.
    In [**6**]: ma.group()
    Out[**6**]: ''
    

    再看一下+?

    #同样先看一下贪婪模式
    In [**13**]: ma = re.match(r'a+','aaaaaaaaaa')
    #会返回最大匹配结果
    In [**14**]: ma.group()
    Out[**14**]: 'aaaaaaaaaa'
    #非贪婪模式下的结果
    In [**15**]: ma = re.match(r'a+?','aaaa')
    #由于+是匹配1个或者无限个前一个字符,所以非贪婪模式下,最小结果也就是返回一个,没毛病.
    In [**16**]: ma.group()
    Out[**16**]: 'a'
    

    续:

    3.边界匹配

    边界匹配可以说是什么实用的一个匹配符号,包括:

    ^ 文档中说:匹配字符串的开头,同时会匹配多行模式下的每一行的开头。
    #先来创建一个多行的字符串
    In [**42**]: str = '''aaafirst
        ...: aaasecond
        ...: aaathird
        ...: '''
    
    #需注意,1.这里用的findall,没有使用match,2.通过re.M将匹配变为多行模式
    In [**55**]: findList = re.findall(r'^aaa.*',str,re.M)
    #我们发现结果为三个
    In [**56**]: findList
    Out[**56**]: ['aaafirst', 'aaasecond', 'aaathird']
    

    千万不要漏掉re.M,否则就会这样:

    #不加re.M
    In [**62**]: findList = re.findall(r'^aaa.*',str)
    #结果只返回了一个
    In [**63**]: findList
    Out[**63**]: ['aaafirst']
    
    $ 与^类似,只是$会匹配字符串末尾
    #首先来创建一个字符串
    In [**64**]: str = ''' firstaaa
        ...: secondaaa
        ...: thirdaaa
        ...: '''
    
    #不要忘记re.M哦
    In [**65**]: ma = re.findall(r'.*aaa$',str,re.M)
    
    In [**66**]: ma
    Out[**66**]: ['firstaaa', 'secondaaa', 'thirdaaa']
    
    \A 文档中:仅仅是匹配字符串开头

    我们用同样的代码看一下\A 与 ^的区别

    #先来创建一个多行的字符串
    In [**42**]: str = '''aaafirst
        ...: aaasecond
        ...: aaathird
        ...: '''
    
    #来试一下re.M情况下的\A,匹配结果
    In [**74**]: ma = re.findall(r'\Aaaa.*',str,re.M)
    #我们可以看到只返回了一个,与^不加re.M的结果一样
    In [**75**]: ma
    Out[**75**]: ['aaafirst']
    #再看一下不带re.M的\A匹配结果
    In [**76**]: ma = re.findall(r'\Aaaa.*',str)
    #不出意外只返回第一个结果
    In [**77**]: ma
    Out[**77**]: ['aaafirst']
    
    \Z 与\A类似,只是\Z仅仅是匹配字符串的末尾

    同样的方式,举个例子

    #首先来创建一个字符串
    #这里请注意不要将'''放到下一行,否则thirdaaa后面会有一个\n将会无法匹配到结果
    
    In [**64**]: str = ''' firstaaa
        ...: secondaaa
        ...: thirdaaa '''
    
    #结果与预想一样,只返回了末尾的这一个
    In [**82**]: ma = re.findall(r'.*aaa\Z',str,re.M)
    
    In [**83**]: ma
    Out[**83**]: ['thirdaaa']
    #不带re.M的也是这样
    In [**84**]: ma = re.findall(r'.*aaa\Z',str)
    
    In [**85**]: ma
    Out[**85**]: ['thirdaaa']
    

    4.分组匹配

    分组匹配可以让匹配更加灵活

    | 匹配左右任意一个分组,相当于或者

    先来假设一个场景:aaa或者bbb

    #让我们来匹配一下aaa
    In [**86**]: ma = re.match(r'aaa|bbb','aaa')
    #成功
    In [**88**]: ma.group()
    Out[**88**]: 'aaa'
    
    #匹配一下bbb
    In [**89**]: ma = re.match(r'aaa|bbb','bbb')
    #同样成功
    In [**91**]: ma.group()
    Out[**91**]: 'bbb'
    
    () ()内将会被看成一个分组

    先来假设一个场景:aa_bb在_处要求可以同时匹配a也可以同时匹配b

    如果没有()我们会怎么写,如果是我,我大概会这样:
    #通过 | 来区分
    In [**92**]: ma = re.match(r'aaabb|aabbb','aaabb')
    #结果没有问题
    In [**93**]: ma.group()
    Out[**93**]: 'aaabb'
    
    使用() 就会方便很多
    #使用(a|b) 将a|b看成一个分组
    In [**94**]: ma = re.match(r'aa(a|b)bb','aaabb')
    #结果没有问题
    In [**95**]: ma.group()
    Out[**95**]: 'aaabb'
    #我们试一下aabbb有没有问题
    In [**96**]: ma = re.match(r'aa(a|b)bb','aabbb')
    
    In [**97**]: ma.group()
    Out[**97**]: 'aabbb'
    
    #这里说一个我自己办的一个愚蠢的事情,当我看到这个问题的时候第一反应只想到了 | 然而结果...
    #本以为 | 只是识别 a|b 结果发现 它是将 aaa看成一组 bbb看成一组
    In [**100**]: ma = re.match(r'aaa|bbb','aaabb')
    
    In [**102**]: ma.group()
    Out[**102**]: 'aaa'
    #虽然是个错误,但对我的理解有很大的帮助
    
    <number> 分组所对应的编号

    每添加一个分组都会对应的生成一个编号
    通过一个经典的例子来看一下,html中的键值对的匹配<root>...</root>

    #由于标签唯一的区别是一个有/一个没有
    #所以这里的正则表达式使用一个分组(\w+>),后面很巧妙的使用\1来补充
    In [**103**]: ma = re.match(r'<(\w+>)\w+</\1','<root>balabala</root>')
    
    In [**104**]: ma
    Out[**104**]: <_sre.SRE_Match at 0x103f0a8a0>
    #查询结果没有问题
    In [**105**]: ma.group()
    Out[**105**]: '<root>balabala</root>'
    #这时候可以查询一下ma的分组,我们会发现,返回的元组中有一个root>的分组
    In [**106**]: ma.groups()
    Out[**106**]: ('root>',)
    
    (?P<name>) 给分组起名字
    (?P=name) 通过分组名使用分组

    由于两个一般是配合使用,这里就写在一起了.
    假如一个表达式中含有多个分组,我们在去用\1\2\3\4去区分,将让代码阅读变得困难,所以我们可以给每个分组起一个名字,其实在我看来,可以将分组看成一个变量,(?P<name>)则是给这个变量起了一个名字这个后面会说
    使用上面的例子:

    #(\w+>)起名为mark,注意:命名需要放在最前面
    In [**110**]: ma = re.match(r'<(?P<mark>\w+>)\w+</(?P=mark)','<root>balabala</root>'
         ...: )
    #查询结果没有问题
    In [**111**]: ma.group()
    Out[**111**]: '<root>balabala</root>'
    #查询groups也能查找到分组
    In [**112**]: ma.groups()
    Out[**112**]: ('root>',)
    

    昨天脑洞大开写了一个这样的问题:

    #想的是模拟一个邮箱验证,前面要求4-10位,后面是@163或者是126,后面.com之后在跟一个163或者126
    In [**115**]: ma = re.match(r'^\w{4,10}@(?P<email>163|126).com(?P=email)','hexiaojia
         ...: n@163.com163')
    #这里是没有问题
    In [**116**]: ma.group()
    Out[**116**]: 'hexiaojian@163.com163'
    
    #前后同为126时也没有问题
    In [**119**]: ma = re.match(r'^\w{4,10}@(?P<email>163|126).com(?P=email)','hexiaojia
         ...: n@126.com126')
    
    In [**121**]: ma.group()
    Out[**121**]: 'hexiaojian@126.com126'
    
    #问题在这,当我想去匹配hexiaojian@163.com126时,匹配失败.
    #我使用的是(163|126)在后面使用时为什么不识别126
    In [**117**]: ma = re.match(r'^\w{4,10}@(?P<email>163|126).com(?P=email)','hexiaojia
         ...: n@163.com126')
    
    In [**118**]: print (type(ma))
    <type 'NoneType'>
    

    想来想去只有一种可能,那就是,当你第一次使用email时就已经给email赋值,当再次使用时会进行检查.

    查阅网址:
    http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html 其实这个要比我的全很多,大家可以仔细研究一番.
    https://docs.python.org/3/library/re.html 因为现在再用3.5了,就不推荐2.7的了

    感谢大家收看,欢迎拍砖

    相关文章

      网友评论

        本文标题:Python正则新解

        本文链接:https://www.haomeiwen.com/subject/ppngvttx.html