精华内容
下载资源
问答
  • 字符串String类中最核心最重要的算法应该算就是字符串匹配算法了,String类中的find(),index(),count()以及split(),...在后面我们将看到Python源码中的字符串匹配算法是基于Boyer-Moore算法,Horspool算法以及Sunda

    字符串String类中最核心最重要的算法应该算就是字符串匹配算法了,String类中的find(),index(),count()以及split(),replace()等操作的基础都是字符串匹配。

    所有字符串匹配算法要处理的根本问题就是当出现不匹配字符时,怎样向后移动模式串。

    在后面我们将看到Python源码中的字符串匹配算法是基于Boyer-Moore算法,Horspool算法以及Sunday算法的混合算法。所以我们先详细介绍一下这三个算法。

    Boyer-Moore算法:

    Boyer-Moore 算法是相当有效的一个算法,真实应用中平均情况下比KMP算法快3-5倍。与KMP算法不同,Boyer-Moore算法在进行比较时是自后向前的,换句话说KMP算法是基于前缀的而Boyer-Moore算法是基于后缀的,基于后缀的优点在于可以更多地跳过文本字符,加快算法。

     1 基于后缀的字符串匹配

     

     

    先引入两个概念:

    坏字符(bad character): 如上图b中a与不匹配情况,则称b为坏字符

    好后缀(good suffix): 如上图中已匹配的阴影部分,称之为好后缀

    当出现不匹配情况时,要将模式串向后移动,Boyer-Moore算法如何计算模式串向后移动的位数呢?

    从坏字符角度考虑:

    1) 如果坏字符b不存在模式串中:

       这时可以直接让模式串全部跳过文本串中的b,如下图

    图 2

     

    2) 如果模式串中也包含坏字符b:

      这时可以让模式串中最右边的b与文本串中的b对齐,如下图

    图 3

    从好后缀角度考虑:

    1) 模式串中有子串和好后缀安全匹配,则将最靠右的那个子串移动到好后缀的位置,如下图:

    图 4

     

    2) 如果不存在和好后缀完全匹配的子串,则在好后缀中找既是模式串前缀又是模式串后缀的最长子串,再按下图所示移动模式串

    图 5

     

    Boyer-Moore算法模式串移动规则:

    现在很容易明白模式串右移的长度为按坏字符计算与按好后缀计算到的较大者,即:

    max{skip(坏字符), skip(好后缀)}

    在很多资料中又把skip(坏字符)记为delta1 table, 把skip(好后缀) 记为delta2 table.

    Horspool算法:

    Horspool算法是首个对Boyer-Moore算法进行简化改进的算法,它认为对于较大的字符集来说,更多的情况是Boyer-Moore算法的坏字符情况能产生更大的移动距离,因为对于大字符集,模式串中出现与好后缀完全匹配的子串或部分匹配的前缀的概率很低。

    Horspool 算法考虑模式串的末尾字符,如果这个字符与文本串中对应的则继续进行后缀搜索,直至匹配成功,或出现不匹配字符,对于后一种情况,则按以下两种情况计算移动距离:

    1) 末尾字符c不在子串P[0,…p_len-2] 中出现,此时可以直接将模式串移动p_len个位置,如下图所示:

    图 6

     

    2) 末尾字符c也在子串p[0,…,p_len-2] 中出现,此时让动模式让子串p[0,…,p_len-2] 中最右边的c与原来的末尾位置对齐,如下图所示:

    图 7

     

    Sunday算法:

    Sunday算法也是对Boyer-Moore算法的简化,原理与Horspool算法相似,区别在于Sunday算法考虑的是文本串中与模式串末尾对齐的字符的下一个字符,Sunday算法按以下两种情况计算模式串移动距离:

    1) 模式串中不存在该字符,此时可以将模式串移动到该字符的下一个字符,如下图:

    图 8

     

    2) 模式串中存在该字符,此时可以移动模式串以使最靠右的该字符与主串中的该字符对齐,如下图:

    图 9

     

    Python源码中字符串匹配算法

    在Python源码stringobject.c中追踪string_find(),string_index(), string_count()三个函数,最后都要追踪到fastsearch.h中的fastsearch()函数fastsearch.h 文件可以在python源码包的Object目录下找到。fastsearch.h里面包含了一些与python实现相关的繁琐细节,为了不让这些细节干扰我们理解算法,我对其中的代码进行了改写:

    1. int fastsearch(char* s, int n, char* p, int m)  
    2. {  
    3.     long mask;  
    4.     int skip;  
    5.     int i, j, mlast, w;  
    6.   
    7.     w = n - m;  
    8.   
    9.     if (w < 0)  
    10.         return -1;  
    11.   
    12.     /* look for special cases */  
    13.     if (m <= 1) {  
    14.         if (m <= 0)//如果模式串为空  
    15.             return -1;  
    16.         /* use special case for 1-character strings */  
    17.         for (i = 0; i < n; i++)  
    18.             if (s[i] == p[0])  
    19.                 return i;  
    20.   
    21.         return -1;  
    22.     }  
    23.   
    24.     mlast = m - 1;  
    25.   
    26.     /* create compressed boyer-moore delta 1 table */  
    27.     skip = mlast - 1;  
    28.     /* process pattern[:-1] */  
    29.     for (mask = i = 0; i < mlast; i++) {  
    30.         mask |= (1 << (p[i] & 0x1F));  
    31.         if (p[i] == p[mlast])  
    32.             skip = mlast - i - 1;  
    33.     }  
    34.   
    35.     /* process pattern[-1] outside the loop */  
    36.     mask |= (1 << (p[mlast] & 0x1F));  
    37.   
    38.     for (i = 0; i <= w; i++) { // w == n - m;  
    39.         /* note: using mlast in the skip path slows things down on x86 */  
    40.         if (s[i+m-1] == p[m-1]) {  //(Boyer-Moore式的后缀搜索)  
    41.             /* candidate match */  
    42.             for (j = 0; j < mlast; j++)  
    43.                 if (s[i+j] != p[j])  
    44.                     break;  
    45.             if (j == mlast) /* got a match! */  
    46.                 return i;  
    47.             /* miss: check if next character is part of pattern */  
    48.             if (!(mask & (1 << (s[i+m] & 0x1F))))  //(Sunday式的基于末字符的下一字符)  
    49.                 i = i + m;  
    50.             else  
    51.                 i = i + skip; //(Horspool式的基于末字符)  
    52.         }  
    53.         else {  
    54.             /* skip: check if next character is part of pattern */  
    55.             if (!(mask & (1 << (s[i+m] & 0x1F))))  
    56.                 i = i + m;  
    57.         }  
    58.     }  
    59.     return -1;  
    60. }  

     

    9~22 行处理的是模式串比文本串长以及模式串为空字符或单字符的特殊情况。

    26~33行创建的是一个压缩了的boyer-moore delta1 table,不是一个table了而只是一个值skip, skip初始化为mlast-1,若在模式串中存在与末尾字符匹配的字符,29~33行代码将skip修改为该字符与末尾之前的间隔。代码中的mask是个布隆过滤器(Bloom filter),用于在后面判断文本串中的字符是否包含在模式串中。

    38~58行进行匹配,先对比末位,若末位匹配,则由前至后进行前缀搜索(42~44行),搜索到了末尾即匹配成功(45~46行),出现不匹配情况时,先考察末位的下一位字符是否包含在模式串中,若包含则将模式串向后移动m位(48~49行),若包含则按依末尾字符计算出来的skip移动。若末位不匹配并且末位的下一位不包含在模式串中,也将模式串向后移动m位(55~56行),若末位不匹配并且末位的下一位包含在模式串中,那么执行for循环位中的i++,让模式串向后移动一位。

    到这里仔细的同学可能和我最初一样心里冒出了三个疑问:

    1.48~49行与55~56行不分明就是Sunday算法式的移动吗?为何在这里只移动了m位,不应该是像图8所示的m+1位吗?

    2.51行不分明就是Horspool算法式的移动吗?为何29~33行计算skip时是

    skip = mlast–i–1 = m-1–i–1,不应该是像图7所示的m-1-i吗?

    3.为何在末字符不匹配,末字符的下一位字符也不包含在模式串中时,只是简单地执行循环体中的i++,让模式串向后移动一位,为何不像图9中所示地移动更多呢? 

    问题1和问题2的答案是相同的,因为在每次for循环之后都会执行i++,所以在循环内部就少加一个1,这个答案很简单,但我看这段代码时困惑了快半个小时了。。。写出来希望其他人看到后少困惑一会。至于问题3我们在后面再讨论。

    现在我们很清晰地看出python源码的fastsearch算法是基于Horspool算法与Sunday算法对Boyer-Moore算法一个简化。fastsearch算法充分利用了Boyer-Moore算法基于后缀的优点,又在出现不匹配情况时结合Sunday算法与Horspool算法确保大多数情况下都产生较大的移动距离。

     

    性能对比

     

    注:考虑本文的篇已经过长,没有结合代码实现来分析各项指标,但在最后附上了以上算法实现的程序文件下载地址

    虽然Boyer-Moore算法与Horspool以及Sunday算法的各项指标都一样,但由于系数和常数项的不同,在实际应用中HorspoolSunday算法是优于Boyer-Moore算法的,

    python fastsearch算法主要改进不仅在实际应用速度上,也在预处理的时间复杂度与空间复杂度上。

    什么是优秀的字符串匹配算法

    这个问题没有绝对的答案,但python源码stringlib的作者Fredrik Lundh这里提出了他的一些标准(fastsearch.h是stringlib的一部分):

    When designing the new algorithm, I used the following constraints:

    在设计新的字符串匹配算法时,我考虑下面的一些要求

    1) should be faster than the current brute-force algorithm for all test cases (based on real-life code), including Jim Hugunin’s worst-case test

      对于任何测试用例都比当前的蛮力算法快,包括Jim Hugunin的最坏情况测试(注:Jim Hugunin ironpython jython之父)

    2) small setup overhead; no dynamic allocation in the fast path (O(m) for speed, O(1) for storage)

      较小的预处理;没有动态分配(O(m)的时间复杂度,O(1)的空间复杂度)

    3) sublinear search behaviour in good cases (O(n/m))

      好情况下亚线性的性能 (O(n/m))

    4) no worse than the current algorithm in worst case (O(nm))

      在最坏情况下不比当前的算法差 (O(nm))

    5) should work well for both 8-bit strings and 16-bit or 32-bit Unicode strings (no O(σ) dependencies)

      对于8比特或16比特字符串以及32比特Unicode字符串都有良好性能(没有O(σ) 依赖)

    6) many real-life searches should be good, very few should be worst case

      大多数实际搜索应该是好的情况,最坏情况很少

    7) reasonably simple implementation

      适当易于实现

    按Fredrik Lundh的说法

     

    This rules out most standard algorithms (Knuth-Morris-Pratt is not sublinear, Boyer-Moore needs tables that depend on both the alphabet size and the pattern size, most Boyer-Moore variants need tables that depend on the pattern size, etc.).

    这个要求淘汰了绝大多数标准算法,Knuth-Morris-Pratt 的最优情况不是亚线性的,Boyer-Moore 算法的模式串预处理得到的两个表(坏字符表,好后缀表)的大小既依赖于模式串大小也依赖于字符集大小,而大多数Boyer-Moore算法变体的模式串预处理需要的表也还是依赖于模式串的大小。

     

    现在可以来给出上面遗留的问题3的答案了,在python fastsearch算法中如果在末字符不匹配并且末字符的下一位字符也不包含在模式串中时,像图8中所示地移动,会要求保存一个模式串长度大小的表,这就与上面第2)点的要求冲突了,Python对内存十分珍视,引用《Python源码剖析》中的一句话“恨不得一块内存掰成两半用”。

     Python fastsearch 的效率如何呢?引用作者的话吧:

     

    The new find implementation is typically 2-10 times faster than the one used in Python 2.3 (using a reasonably representative set of test strings, that is). For the find portions of the stringbench test suite, the new algorithm is up to 26 times faster.

    再提一下fastsearch代码中的int型变量mask,前面说了这是个布隆过滤器(Bloom filter),对python这样的支持32 bit Unicode 字符的语言来说,如果用数组或hash表来实现判断文本串中某字符是否包含于模式串中,就会浪费大量内存,这种低效的实现方式显然是无法允许的,所以这里有个优雅的布隆过器,仅用一个int型变量就能判断所有Unicode字符是否包含于某字符串,大大节约了内存。布隆过滤器又是个说来话长的话,还是赶快打住吧,考虑下下一篇是不是结合python源码讲一下这个话题,相关资料可以参考:

    http://www.google.com.hk/ggblog/googlechinablog/2007/07/bloom-filter_7469.html

    http://blog.csdn.net/jiaomeng/archive/2007/01/27/1495500.aspx

    算法实现:

    由于这篇啰嗦的有点长了,我就不把Boyer-Moore,Horspool,Sunday 的代码贴出来了,我把程序文件做了个压缩包,需要的可以在下面的链接下载。www.endless-loops.com/wp-content/uploads/2011/02/StringSearch.zip

    展开全文
  • 方法一:也是最简单的 直接使用pd.to_datetime函数实现 data['交易时间'] = pd.to_...注意使用datetime包中后面的字符串匹配需要和原字符串的格式相同,才能转义过来,相当于yyyy-mm-dd格式的需要按照’%Y-%M-%D’来实
  • py匹配字符串中间的字符串

    千次阅读 2017-12-05 19:25:36
    python 正则表达式,怎样匹配以某个字符串开头,以某个字符串结尾的情况? str ="abcdefg123213qwe" 正则表达式:^abc(.*?)qwe$

    python 正则表达式,怎样匹配以某个字符串开头,以某个字符串结尾的情况?

    str ="abcdefg123213qwe"

    正则表达式:^abc(.*?)qwe$

    dbconfig = open("./config.xml", "r")
    configStr = dbconfig.read();


    pattern = re.compile("<HostName>(.*?)</HostName>")
    HostName = re.findall(pattern, configStr)[0]

    展开全文
  • 词语是自然语言处理中重要的知识...分词主要有基于字符串匹配的方法、基于规则的方法和基于统计的方法,本文主要通过python实现基于字符串匹配的方法也称为机械分词中的两类方法:正向最大匹配算法和逆向最大匹配算法,

            词语是自然语言处理中重要的知识载体和基本操作单元, 但是中文里词与词之间没有很明显的标记,它们都是连续的字符串,所以文本处理的第一步是怎样进行中文分词。分词是添加合适的显性的词语边界标志使得所形成的词串反映句子的本意的过程。分词主要有基于字符串匹配的方法、基于规则的方法和基于统计的方法,本文主要通过python实现基于字符串匹配的方法也称为机械分词中的两类方法:正向最大匹配算法和逆向最大匹配算法,有实验表明逆向最大匹配算法比正向最大匹配算法更有效。

           基于字符串匹配的分词法是一种的机械分词方法,根据分词词典和一个基本的切分规则,最常用的规则为最长匹配原则。最长匹配方法的思想简单:一个正确的分词结果应该由合法的词组成,而这些词是根据所选词典中的词比较分出。按照所扫描方向的不同,分词的匹配方法分为正向匹配和逆向匹配;按照长度不同优先匹配的情况,分为最长匹配和最短匹配;按照是否与词性标注的过程相结合又可分为分词与标注相结合的一体化和单纯的分词方法。

           1、正向最大匹配算法

          正向最大匹配算法(MM)的思想是假设自动分词中最长词条所含汉字的个数为n, 则截取需要分词文本中当前字符串序列中的前n个字符作为匹配字段,查找分词词典,若词典中有这样一个n字词那么就匹配成功,匹配字段作为一个词被切分出来;若词典中找不到这样的一个n字词那么匹配失败, 匹配字段去掉最后一个汉字, 剩下的字符作为新的匹配字段再进行匹配,如此进行下去,直到匹配成功为止。因为停用词对后续文本处理可能会造成干扰,所以在用正向最大匹配算法分词的过程中直接去除了停用词。具体步骤如下:

          S1、导入分词词典words.txt,存储为字典形式dic、导入停用词词典stoplis.txt ,存储为字典形式stoplis、需要分词的文本文件 test.txt,存储为字符串chars

          S2、遍历分词词典,找出最长的词,其长度为此算法中的最大分词长度max_chars 

          S3、创建空列表words存储分词结果

          S4、初始化字符串chars的分词起点n=0

          S5、判断分词点n是否在字符串chars内,即n < len(chars)  如果成立,则进入下一步骤,否则进入S9

          S6、根据分词长度i(初始值为max_chars)截取相应的需分词文本chars的字符串s 

          S7、判断s是否存在于分词词典中,若存在,则分两种情况讨论,一是s是停用词,那么直接删除,分词起点n后移i位,转到步骤5;二是s不是停用词,那么直接添加到分词结果words中,分词起点n后移i位,转到步骤5;若不存在,则分两种情况讨论,一是s是停用词,那么直接删除,分词起点后移i位,转到步骤5;二是s不是停用词,分词长度i>1时,分词长度i减少1,转到步骤6 ,若是此时s是单字,则转入步骤8;

          S8、将s添加到分词结果words中,分词起点n后移1位,转到步骤5

          S9、将需分词文本chars的分词结果words输出到文本文件result.txt中

           具体实现代码如下:

    import codecs
    
    #获得分词字典,存储为字典形式
    f1=codecs.open('words.txt','r',encoding='gbk')
    dic={}
    while 1:
    	line=f1.readline()
    	if len(line)==0:
    		break
    	term=line.strip().replace('\r\n','')#去除字符串两侧的换行符,避免取最大分词长度时出差错
    	dic[term]=1
    f1.close
    
    
    #获得需要分词的文本,为字符串形式
    f2=codecs.open('test.txt','r',encoding='gbk')
    chars=f2.read().strip().replace('\r\n','') #去除字符串两侧的换行符,避免截词时出差错
    f2.close
    
    #获得停用词典,存储为字典形式
    f3=codecs.open('stoplis.txt','r',encoding='gbk')
    stoplis={}
    while 1:
    	line=f3.readline()
    	if len(line)==0:
    		break
    	term=line.strip()
    	stoplis[term]=1
    f3.close
    
    #正向最大匹配分词算法
    
    #遍历分词字典,获得最大分词长度
    max_chars=0
    for key in dic:
        if len(key)>max_chars:
            max_chars=len(key)
    		
    #定义一个空列表来存储分词结果
    words=[]
    n=0
    while n<len(chars):
            matched = 0 
            for i in xrange(max_chars,0,-1): 
                    s=chars[n:n+i]
                    #判断所截取字符串是否在分词词典和停用词典内
                    if s in dic:
                           if s in stoplis:
                               matched=1
                               n=n+i
                               break
                           else: 
                               words.append(s.encode('gbk'))
                               matched=1
                               n=n+i
                               break
                    if s in stoplis:
                        matched=1
                        n=n+i
                        break
            if not matched:
                words.append(chars[n].encode('gbk'))
                n=n+1
            
    #将分词的结果存入新的文本文件中                       
    f3=open('result.txt','w')
    f3.write("\t".join(words))
    f3.close



            

    展开全文
  • 正則表達式(简称RE)本质上能够看作一个小的、高度专业化的...然后您能提这种问题:“这个字符串匹配这个模式吗?”,或者“在这个字符串中存在这个模式的匹配吗?”。你也能使用正則表達式改动一个字符串或者分...
    正則表達式(简称RE)本质上能够看作一个小的、高度专业化的编程语言,在Python中能够通过re模块使用它。使用正則表達式,你须要为想要匹配的字符串集合指定一套规则,字符串集合能够包括英文句子、e-mail地址、TeX命令或者其他不论什么你希望的字符串。然后您能提这种问题:“这个字符串匹配这个模式吗?”,或者“在这个字符串中存在这个模式的匹配吗?”。你也能使用正則表達式改动一个字符串或者分离它。
    正則表達式被编译到一系列的字节码,然后被C语言实现的匹配引擎运行。

    在一些高级应用场景,必须关注引擎怎么运行一个RE,以依据引擎的特征编写RE提高字节码的处理效率。这篇文章不包括优化,优化须要对匹配引擎的内部实现有好的理解。

    正則表達式相对小而且存在限制,所以不是全部的字符串处理任务都能用正則表達式解决。也存在有些任务能够用正則表達式做,但表达式很复杂。在这些情况下,更好的选择是使用Python代码处理,但Python代码相对正則表達式会更慢。但却可能更好理解。

    正則表達式简述

    我们将从最简单的正則表達式開始,因为正則表達式被用于字符串的操作,我们将从最经常使用的任务開始:匹配字符。

    匹配字符串

    大部分字母和字符将匹配他们自身,比如。正則表達式test将匹配字符串test(你能够開始大写和小写不敏感模式,这样RE能够匹配Test或者TEST)。
    这个规则存在例外,一些字符是特殊元字符。不匹配他们自身。他们暗示一些不平常的事将被匹配,或者他们影响RE的其他部分,比如反复他们或者改变他们的含义。文章的其余部分都主要讨论各种元字符和他们的含义。
    以下是元字符的列表,后面将介绍他们的含义:
    . ^ $ * + ? { } [ ] \ | ( )
    首先我们看[和],他们被用于指定一个字符类。表示你希望匹配的一套字符集。

    字符能被单独列出,或者使用'-'来指示字符的范围,比如:[abc]将匹配字符a、b或c的随意一个;[a-c]也是匹配a、b或c中的随意一个。假设你想匹配仅小写字母。那么RE应该为[a-z]。
    在类中([ ]内)的元字符不是激活的,比如:[akm$]将匹配'a'、'k'、'm'或'$'中的随意一个,'$'是一个元字符,可是在字符类中它作为普通字符使用。
    你也能排除类中列出的字符集,通过将'^'作为类的第一个字符,注意在类之外的'^'将只匹配'^'字符,比如:[^5]将匹配除了5之外的不论什么字符。
    也许最重要的元字符是反斜杠\。在Python中,反斜杠能被各种字符尾随作为各种特殊序列使用。它也能被用于取出元字符的特殊性将其作为本身匹配。比如:假设你须要匹配一个[或者\。你能在它们之前带上一个反斜杠移除它们的特殊含义,即\[或者\\。
    以'\'開始的特殊序列中的一些表示了常常被使用的提前定义字符集。比如数字集合、字符集合、或者非空白的随意字符集合。
    让我们看一个样例:\w匹配不论什么字母数字。

    假设正則表達式模式以字节为单位,这等价于类[a-zA-Z0-9_]。

    假设正則表達式模式是字符串。则\w将匹配全部被unicodedata模块提供的在Unicode数据库总的字符。当编译正則表達式时,你能加入re.ASCII标志给\w更严格的限制。


    以下提供了部分特殊序列供參考:
    \d:匹配随意数字。等价于[0-9];
    \D:匹配随意非数据字符。等价于[^0-9];
    \s:匹配随意空白字符,等价于[ \t\n\r\f\v]。
    \S:匹配随意非空白字符,等价于[^ \t\n\r\f\v]。
    \w:匹配随意字母数字,等价于[a-zA-Z0-9_];
    \W:匹配随意非字母和数字。等价于[^a-zA-Z0-9_]。
    这些序列能够被包括到字符类中,比如:[\s,.]是一个字符类。将匹配随意的空白字符,或者',',或者'.'。


    在这节中最后的元字符是'.',它匹配除了新行字符之外的不论什么字符,使用交替模式(re.DOTALL)它将匹配包含新行的全部字符,'.'通常被用于须要匹配“随意字符”的场景。

    处理反复

    正則表達式的首要功能是匹配字符集,而正則表達式的还有一个能力则是指定RE中特定部分必须被反复多少次。
    处理反复的第一个元字符是'*'。'*'不会匹配字符'*',它表示先前的字符能被匹配0次或者多次。
    比如:ca*t将匹配ct(0个a)、cat(1个a)、caaat(3个a)、等等。RE引擎内部会限制a的匹配的数量,但通常足够了。
    反复(比如*)算法是贪婪的,对于反复的RE,匹配引擎将尝试尽可能多的反复次数,假设模式的后面部分不匹配,则匹配引擎将回退并再次尝试更少的反复次数。
    比如,考虑表达式a[bcd]*b。这匹配单词'a'。0个或者多个来自类[bcd]的字母,最后以'b'结束。以下是RE匹配abcbd的过程:
    1、匹配a:RE匹配a成功;
    2、匹配abcbd:引擎匹配[bcd]*。因为尽可能的匹配很多其它,所以匹配了整个字符串;
    3、匹配失败:引擎试着匹配b,可是已经到达字符串结尾,因此失败。
    4、匹配abcb:回退,[bcd]*匹配降低一个字符;
    5、匹配失败:再次尝试b,但当前位置的字符为d;
    6、匹配abc:继续回退。以至于[bcd]*仅匹配bc。
    7、匹配abcb:再次尝试b。这次当前位置的字符为b,匹配成功,结束。


    RE终于匹配abcb,整个过程演示了匹配引擎的匹配过程。首先匹配尽可能多的字符,假设不匹配。则不断回退再次尝试。它将回退直到[bcd]*匹配0个字符,假设任然失败,则引擎得出结论“字符串不匹配RE”。
    还有一个反复的元字符是+。匹配一次或者多次。小心*和+之间的不同。*匹配0次或者多次。即能够匹配空;+则须要至少出现一次。

    比如:ca+t将匹配cat(1个a),caaat(3个a),但不匹配ct。
    另外还有两个反复限定符。其一是问号'?',表示匹配一次或者0次,比如:home-?brew匹配homebrew或者home-brew。
    最复杂的反复限定符是{m,n},当中m和n都是正整数,表示至少匹配m次。最多匹配n次。

    比如:a/{1,3}b将匹配a/b,a//b,和a///b,它将不匹配ab。或者ab。


    你能忽略m或者n,忽略m表示最小值为0,而忽略n表示无限制。
    你可能已经注意到,使用最后一个限定符能够代替前面3个限定符:{0,}等价于*;{1,}等价于+;{0,1}等价于?。

    为什么使用*、+或者?

    呢?主要在于,更简短的表达式更利于阅读和理解。


    使用正則表達式

    如今我们已经了解了正則表達式的基本的语法,以下看在Python中怎么使用正則表達式。

    re模块提供了使用正則表達式的接口,同意你编译RE到对象。然后使用它们。


    编译正則表達式

    正則表達式被编译到模式对象。提供了各种操作的方法,比如模式匹配或者替换。


    >>> import re
    >>> p = re.compile('ab*')
    >>> p
    re.compile('ab*')
    re.compile()也提供了一个可选的flags參数。用于激活各种特征。后面将具体介绍,以下是一个简单的样例:
    >>> p = re.compile('ab*', re.IGNORECASE)
    RE作为一个字符串传给re.compile()。RE被作为字符串处理是由于正則表達式不是Python语言核心的一部分,没有特定的语言用于创建它们。

    re模块不过Python包括的一个C语言扩展模块,就像socket和zlib模块一样。


    将RE作为字符串保持了Python语言的简单,但也存在不利,比例如以下一节将讲述的内容。

    反斜杠问题

    如前所述,正則表達式使用反斜杠来表示一些特殊组合或者同意特殊字符作为普通字符使用。这一点和Python对于发斜杠的使用冲突。
    你假设想写一个RE匹配字符串\section。我们看看怎么构造一个正則表達式对象:首先。我们使用整个字符串作为正則表達式;其次。找出反斜杠和其他元字符,在它们前面加入反斜杠,变为\\section;最后,字符串被传入到re.compile()。因为传入的必须为\\section。结合Python语法,每一个\的前面必须再次加入一个\,因此,终于在Python中传入的字符串为"\\\\section"。
    简而言之。为了匹配一个反斜杠。在Python中你须要写'\\\\'作为RE字符串。这导致了非常多反复的反斜杠,使语法非常难于理解。
    解决方式是为正則表達式使用Python的原生字符串凝视。当字符串带有前缀'r'时。反斜杠将不以特殊字符处理,于是r"\n"是包括'\'和'n'的两个字符的字符串,而"\n"是包括换行符的一个字符的字符串。在Python中正則表達式将常常採用这样的方式编写。


    运行匹配

    一旦你有一个已编译的正則表達式对象,你就能够使用该对象的方法和属性,以下做一个简单的介绍。
    1)match()
    确定RE是否匹配字符串的开头。


    2)search()
    扫描字符串,查找和RE匹配的不论什么位置。


    3)findall()
    找到全部RE匹配的子字符串,并作为一个列表返回。
    4)finditer()
    发现全部RE匹配的子字符串,并作为一个iterator返回。
    假设找到匹配。match()和search()返回None;假设匹配成功。则返回一个匹配对象实例,包括匹配的信息:開始和结束点、匹配的子字符串、等等。
    以下来看看Python中怎么使用正則表達式。
    首先。执行Python解释器,导入re模块,而且编译一个RE:

    >>> import re
    >>> p = re.compile('[a-z]+')
    >>> p
    re.compile('[a-z]+')
    如今。你能尝试匹配各种字符串,一个空字符串将根本不匹配,因为+意味着‘一个或者很多其它’,match()将返回None,你能直接打印结果:
    >>> p.match("")
    >>> print(p.match(""))
    None
    接下来。我们尝试一个匹配的字符串,这时。match()将返回一个匹配对象,因此你应该存储结果在一个变量中以供后面使用:
    >>> m = p.match('tempo')
    >>> m  
    <_sre.SRE_Match object; span=(0, 5), match='tempo'>
    如今你能询问匹配对象关于匹配字符串的信息。匹配对象也有几个方法和属性,最重要的几个是:
    1)group()
    返回被RE匹配的字符串
    2)start()
    返回匹配的開始位置
    3)end()
    返回匹配的结束位置
    4)span()
    返回包括匹配位置的元组(開始,结束)
    以下是一些使用这些方法的样例:
    >>> m.group()
    'tempo'
    >>> m.start(), m.end()
    (0, 5)
    >>> m.span()
    (0, 5)
    因为match()仅检查RE是否匹配字符串的開始,start()将总是返回0。然而,search()方法扫描整个字符串。因此開始位置不一定为0:
    >>> print(p.match('::: message'))
    None
    >>> m = p.search('::: message'); print(m)  
    <_sre.SRE_Match object; span=(4, 11), match='message'>
    >>> m.group()
    'message'
    >>> m.span()
    (4, 11)
    在实际编程汇总,通常将匹配对象存入一个变量中,然后检查它是否为None。比如:
    p = re.compile( ... )
    m = p.match( 'string goes here' )
    if m:
        print('Match found: ', m.group())
    else:
        print('No match')
    findall()返回匹配字符串的列表:
    >>> p = re.compile('\d+')
    >>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
    ['12', '11', '10']
    findall()在返回结果前必须创建完整的列表,而finditer()则返回匹配对象实例作为一个iterator:
    >>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
    >>> iterator  
    <callable_iterator object at 0x...>
    >>> for match in iterator:
    ...     print(match.span())
    ...
    (0, 2)
    (22, 24)
    (29, 31)

    模块级函数

    你不是一定须要创建一个模式对象然后调用它的方法,re模块也提供了模块级的函数match()、search()、findall()、sub()、等等。

    这些函数採用和相应的模式方法相同的參数,也相同返回None或者匹配对象实例:

    >>> print(re.match(r'From\s+', 'Fromage amk'))
    None
    >>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')  
    <_sre.SRE_Match object; span=(0, 5), match='From '>
    这些函数创建一个模式对象,并调用它上面的方法。它们也存储编译后的对象到缓存中,以至于未来使用相同的RE将不须要又一次编译。
    你应该用这些模块及的函数。还是应该通过模块对象来调用呢?假设你正在做一个正則表達式的循环,则预编译将节省很多函数调用,否则。两个方式没有太大差别。

    编译标志

    编译标志让你改动正則表達式怎样工作的一些方面。

    在re模块中标志能够使用两种名称,长名称。比如IGNORECASE。和短名称,比如I。通过位或运算,多个标志能被指定,比如re.I | re.M设置I和M标志。
    以下是可用标志的列表和每一个标志的解释:
    1)ASCII, A
    当使用\w、\b、\s和\d时仅匹配ASCII字符;
    2)DOTALL, S
    使'.'匹配不论什么字符,包含新行;
    3)IGNORECASE, I
    忽略大写和小写匹配。
    4)LOCALE, L
    做地域相关匹配。
    5)MULTILINE, M
    多行匹配,影响^和$。
    6)VERBOSE, X (for ‘extended’)
    启动具体的RE,能更清晰的组织和更好理解。
    比如。以下使用了re.VERBOSE。使RE更easy阅读:

    charref = re.compile(r"""
     &[#]                # Start of a numeric entity reference
     (
         0[0-7]+         # Octal form
       | [0-9]+          # Decimal form
       | x[0-9a-fA-F]+   # Hexadecimal form
     )
     ;                   # Trailing semicolon
    """, re.VERBOSE)
    假设没有使用re.VERBOSE,则RE将是这样:
    charref = re.compile("&#(0[0-7]+"
                         "|[0-9]+"
                         "|x[0-9a-fA-F]+);")
    在上面的样例中,Python的自己主动字符串串联被用于将RE分化到多个片段,可是它任然比使用re.VERBOSE更难理解。

    正則表達式的很多其它特性

    到眼下为止我们仅覆盖了正則表達式的部分特性,在这里,我们将探索一些新的特性。


    很多其它元字符

    这里我们将介绍很多其它的元字符。
    1)|
    表示‘或’操作,假设A和B都是正則表達式,则A|B表示匹配A或者匹配B。为了能有效的工作,|有非常低的优先级,比如:Crow|Servo将匹配Crow或者Servo。而不是Cro。一个‘w’或者一个‘S’。再接上ervo。
    为了匹配字符'|',你须要使用\|,或者封装它到一个字符类中。作为[|]。
    2)^
    匹配行的開始。

    除非设置了MULTILINE标志。这将仅匹配字符串的開始。

    在MULTILINE模式下。这也匹配每一个新行的開始。
    比如:假设你希望匹配在行的開始匹配单词From,RE中将使用^From。

    >>> print(re.search('^From', 'From Here to Eternity'))  
    <_sre.SRE_Match object; span=(0, 4), match='From'>
    >>> print(re.search('^From', 'Reciting From Memory'))
    None
    3)$
    匹配行的结尾。

    能够是字符串的结尾,或者是被新行符尾随的部分。

    >>> print(re.search('}$', '{block}'))  
    <_sre.SRE_Match object; span=(6, 7), match='}'>
    >>> print(re.search('}$', '{block} '))
    None
    >>> print(re.search('}$', '{block}\n'))  
    <_sre.SRE_Match object; span=(6, 7), match='}'>
    为了匹配字符'$',须要使用\$或者将它分装到字符类中,作为[$]。
    4)\A
    仅匹配字符串的開始。当不使用MULTILINE模式时,\A和^是同样的;在MULTILINE模式,他们是不同的:\A任然仅匹配字符串的開始。而^能够匹配每一个新行的開始。
    5)\Z
    匹配仅在字符串末尾。
    6)\b
    单词边界。这是一个零宽度断言,仅匹配单词的開始和结束。

    一个单词被定义为字母的序列。以至于单词的结束被表示为空白或者一个非字母字符。


    以下是一个样例。匹配单词class,但它位于一个单词内部时将不被匹配:

    >>> p = re.compile(r'\bclass\b')
    >>> print(p.search('no class at all'))  
    <_sre.SRE_Match object; span=(3, 8), match='class'>
    >>> print(p.search('the declassified algorithm'))
    None
    >>> print(p.search('one subclass is'))
    None
    在使用时有两点须要注意:首先,在Python中,\b表示退格字符。ASCII值是8,假设你不用原始字符串,那么Python将转换\b到一个退格字符,你的RE将不按你的设想匹配。以下的样例和我们上面的样例相似。仅有的差别是RE字符串少了'r'前缀:
    >>> p = re.compile('\bclass\b')
    >>> print(p.search('no class at all'))
    None
    >>> print(p.search('\b' + 'class' + '\b'))  
    <_sre.SRE_Match object; span=(0, 7), match='\x08class\x08'>
    第二,在字符类里,\b表示退格字符,和Python中的含义表示一致。
    7)\B
    还有一个零宽度断言。和\b相反,仅匹配当前位置不是单词边界。

    分组

    组通过'('和')'元字符标识,'('和')'在这里和数学表达式中的含义同样,它们将内部的表达式归为一个分组。你能指定一个分组反复的次数,通过使用反复元字符*、+、?或者{m,n}。

    比如。(ab)*将匹配0个或者多个ab。

    >>> p = re.compile('(ab)*')
    >>> print(p.match('ababababab').span())
    (0, 10)
    组也能获取它们匹配的字符串的開始和结束点。通过传递一个參数到group()、start()、end()和span()。组的编号从0開始,组0总是存在的,他就是整个RE,因此匹配对象方法将组0作为他们的默认參数。


    >>> p = re.compile('(a)b')
    >>> m = p.match('ab')
    >>> m.group()
    'ab'
    >>> m.group(0)
    'ab'
    子组从左到右编号,从1開始。组能是嵌套的。为了确定编号,从左向右仅仅算开放括号字符。


    >>> p = re.compile('(a(b)c)d')
    >>> m = p.match('abcd')
    >>> m.group(0)
    'abcd'
    >>> m.group(1)
    'abc'
    >>> m.group(2)
    'b'
    group()一次能被传递多个组编号。这样的情况下它将返回一个元组:
    >>> m.group(2,1,2)
    ('b', 'abc', 'b')
    groups()方法返回包括全部子组匹配的字符串的元组,子组从1開始:
    >>> m.groups()
    ('abc', 'b')
    在模式中的反向应用同意你指定一个先前组的内容,比如,\1表示在当前位置的内容和组1匹配的内容同样。

    注意在Python中必须使用原始字符串表示。
    比如,以下的RE探測同一时候出现两个同样单词的情况:

    >>> p = re.compile(r'(\b\w+)\s+\1')
    >>> p.search('Paris in the the spring').group()
    'the the'
    这样的匹配方式在搜索中非常少使用。但在字符串替换时却非常实用。

    非捕获和命名组

    RE能够使用很多组,用于捕获感兴趣的子字符串或者使复杂的RE结构更清晰。这使通过组编号进行跟踪变得很困难。

    有两个方法能够解决问题,我们首先看第一个。
    有时你将想要使用一个组表示正則表達式的一部分,可是不想要获取该组的内容。

    这时,你能使用非捕获组:(?

    :...),将...替换为不论什么正則表達式。

    >>> m = re.match("([abc])+", "abc")
    >>> m.groups()
    ('c',)
    >>> m = re.match("(?:[abc])+", "abc")
    >>> m.groups()
    ()
    除了你不能获取组匹配的内容,一个非捕获组的行为和捕获组的行为全然一致。你能放不论什么内容在它里面,能够使用反复元字符(比如*)反复它,或者嵌套其他组(捕获或者非捕获)。

    当改动一个已经存在的模式时(?:...)是特别实用的,由于你能够添加新的组而不改变已有的组的编号。但须要注意,使用非捕获组和捕获组在匹配上没有不论什么效率上的不同。


    还有一个更有意义的特征是命名组:代替为组编号,改为使用为组指定一个名称。
    命名组是Python特定扩展之中的一个,语法为:(?

    P<name>...),name是组的名称。

    匹配对象方法能够接受组的编号或者组的名称,因此你能使用两种方法得到组的匹配信息:

    >>> p = re.compile(r'(?P<word>\b\w+\b)')
    >>> m = p.search( '(((( Lots of punctuation )))' )
    >>> m.group('word')
    'Lots'
    >>> m.group(1)
    'Lots'
    命名组是便利的。由于名称比编号更easy记忆。以下是一个来自imaplib模块的RE的样例:
    InternalDate = re.compile(r'INTERNALDATE "'
            r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
            r'(?

    P<year>[0-9][0-9][0-9][0-9])' r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])' r' (?

    P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])' r'"')

    显然使用名称的方式m.group('zonem')比使用组编号9获取匹配值的方式更加easy使用。


    对于向后应用的语法。比如(...)\1,引用了组的编号,使用组名取代编号语法有一些改变。这是还有一个Python扩展:(?P=name)。表示组名为name的内容应该和当前点的内容匹配。为发现2个连续反复单词的正則表達式,(\b\w+)\s+\1能被写为(?P<word>\b\w+)\s+(?

    P=word):

    >>> p = re.compile(r'(?P<word>\b\w+)\s+(?

    P=word)') >>> p.search('Paris in the the spring').group() 'the the'

    预測先行断言

    还有一个零宽度断言是预測先行断言。预測先行断言能够在正、负形式使用。像这样:
    1)(?

    =...)
    正预測先行断言。

    假设包括...表示的正則表達式在当前位置被成功匹配,则成功,否则失败。可是,尽管包括的正則表達式被尝试。但匹配引擎并不会前进,模式的其余部分还是从断言開始的地方開始匹配。


    2)(?!...)
    负预測先行断言。和正预測先行断言相反,假设它包括的正則表達式不匹配当前位置的字符串,则成功。


    为了使描写叙述更加详细,我们看一个样例说明预測先行的作用。考虑一个简单的模式,用于匹配一个文件名称,并将它拆分为文件名称和扩展名。比如。news.rc中,news表示文件名称,rc表示扩展名。
    匹配的模式非常easy:


    .*[.].*$


    注意.须要放到字符类中。由于它是一个元字符;也注意$,用于确保全部字符串的其余部分被包括在扩展中。这个正則表達式能够匹配foo.bar、autoexec.bat、sendmail.cf和printers.conf。
    如今,考虑一个稍复杂点的情况,假设你想匹配扩展名不是bat的文件名称该怎么做?以下是一些不对的尝试:
    1).*[.][^b].*$
    这个尝试要求扩展名的第一个字符不是b来排除bat。这时错误的。由于该模式也不匹配foo.bar。


    2).*[.]([^b]..|.[^a].|..[^t])$
    这个比上一个更复杂一点,要求:扩展的第一个字符不匹配b,或者第二个字符不匹配a,或者第三个字符不匹配t。

    这个模式匹配foo.bar,不匹配autoexec.bat,可是它要求扩展名必须为3个字符。将不匹配带有2个字符扩展名的文件,比如sendmail.cf。我们将继续完好它。
    3).*[.]([^b].?.?

    |.[^a]?.?|..?

    [^t]?)$
    在这个尝试中,第二个和第三个字符都是可选的,为了匹配的扩展名小于三个字符的情况,比如sendmail.cf。
    如今模式開始复杂起来了,開始难于阅读和理解。

    更糟的是,假设问题改变。你想同一时候排除扩展名bat和exe,模式将变得更为复杂和难于理解。
    一个负预測先行断言能够解决问题。
    .*[.](?!bat$).*$
    含义为:假设当前点表达式bat不匹配,则尝试模式的其余部分。假设bat$匹配。整个模式将失败。

    结尾的$用于防止出现sample.batch的情况。
    排除还有一个文件扩展名如今也easy了,简单的添加它作为断言的二选一。以下的模式同一时候排除bat和exe:
    .*[.](?

    !bat$|exe$).*$

    改动字符串

    眼下为止。我们仅适用正則表達式查询字符串,正則表達式也可用于改动字符串,使用以下的方法:
    1)split()
    从RE匹配的地方将字符串分解为字符串列表。
    2)sub()
    找到RE匹配的全部子字符串。并使用不同的字符串代替它们;
    3)subn()
    和sub做的事同样,可是返回新字符串和替换的次数。

    分解字符串

    split()方法用于分解一个字符串。使用RE匹配的子字符串作为分隔符。返回分解后的子字符串列表。

    它和字符串的split()方法是类似的。可是提供了更为通用的分隔符。字符串的split()方法仅支持空格或者固定的字符串。

    re也提供了一个模块级的re.split()函数。
    split(string[, maxsplit=0]) 
    通过正則表達式的匹配分解字符串。

    假设在RE中使用了括号,则正則表達式的匹配也将出如今结果列表中。

    假设maxsplit值大于0。则最多做maxsplit次分解。
    你能通过设置maxsplit的值限制分解的数量。当maxsplit大于0时,最多进行maxsplit次分解,字符串的剩余部分被作为列表的最后一个元素返回。在以下的样例中,分隔符时不论什么非字符或数字的字符组合:

    >>> p = re.compile(r'\W+')
    >>> p.split('This is a test, short and sweet, of split().')
    ['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
    >>> p.split('This is a test, short and sweet, of split().', 3)
    ['This', 'is', 'a', 'test, short and sweet, of split().']
    有时你不仅对分隔符之间是什么感兴趣,并且须要知道哦分隔符是什么。假设在RE中使用了括号。那么他们的值也将出如今返回列表中。比較以下的调用:
    >>> p = re.compile(r'\W+')
    >>> p2 = re.compile(r'(\W+)')
    >>> p.split('This... is a test.')
    ['This', 'is', 'a', 'test', '']
    >>> p2.split('This... is a test.')
    ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
    模块级的函数re.split()添加了RE作为第一个參数,其余的同样:
    >>> re.split('[\W]+', 'Words, words, words.')
    ['Words', 'words', 'words', '']
    >>> re.split('([\W]+)', 'Words, words, words.')
    ['Words', ', ', 'words', ', ', 'words', '.', '']
    >>> re.split('[\W]+', 'Words, words, words.', 1)
    ['Words', 'words, words.']

    替换

    还有一个常见的操作是发现字符串中的全部匹配,并将其替换为还有一个字符串。sub()方法传入參数replacement,能够是一个字符串。或者一个函数。


    sub(replacement, string[, count=0]) 
    返回替换后的字符串,替换採用从左到右而且非重叠的方式。

    假设模式未被发现。返回未改变的字符串。


    可选參数count用于指定替换的最大次数。count必须非负。

    默认值0意味着替换全部。


    以下是一个简单的样例。

    它使用colour替换全部匹配的颜色名:

    >>> p = re.compile( '(blue|white|red)')
    >>> p.sub( 'colour', 'blue socks and red shoes')
    'colour socks and colour shoes'
    >>> p.sub( 'colour', 'blue socks and red shoes', count=1)
    'colour socks and red shoes'
    subn()方法做相同的事,可是返回一个长度为2的元组,包括新字符串和替换的次数:
    >>> p = re.compile( '(blue|white|red)')
    >>> p.subn( 'colour', 'blue socks and red shoes')
    ('colour socks and colour shoes', 2)
    >>> p.subn( 'colour', 'no colours at all')
    ('no colours at all', 0)
    空匹配仅仅有当不和前一个匹配相邻时才做替换:
    >>> p = re.compile('x*')
    >>> p.sub('-', 'abxd')
    '-a-b-d-'
    假设replacement是一个字符串。在它里面的不论什么反斜杠转义符都会被处理。

    即,\n会被转换为一个新行字符。\r被转换为回车符,等等。未知的转义符比如\j被遗留。

    反向引用,比如\6,被RE中的相应组匹配的子字符串代替。

    这让你在替换后的结果字符串中能合并原始字符串的部分。
    以下的样例匹配单词section。被一个{}包括的字符串尾随,而且改变section到subsection:

    >>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
    >>> p.sub(r'subsection{\1}','section{First} section{second}')
    'subsection{First} subsection{second}'
    也能够使用(?P<name>...)命名的组。

    \g<name>将通过组名来匹配,\g<number>将通过组编号来匹配。因此\g<2>等价于\2,当能够避免歧义,比如\g<2>0表示匹配组2。而\20则会被解释为匹配组20。以下替换的样例都是等价的,可是使用了3种不同的方式:

    >>> p = re.compile('section{ (?

    P<name> [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<1>}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<name>}','section{First}') 'subsection{First}'

    replacement也能够是一个函数,能够给你很多其它的控制。假设replacement是一个函数。函数会处理每个模式匹配的非重叠的子字符串。在每次调用。函数被传递一个匹配对象作为參数。函数能够使用这个信息计算替换字符串并返回它。
    在以下的样例中。replacement函数转换10进制数到16进制:
    >>> def hexrepl(match):
    ...     "Return the hex string for a decimal number"
    ...     value = int(match.group())
    ...     return hex(value)
    ...
    >>> p = re.compile(r'\d+')
    >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
    'Call 0xffd2 for printing, 0xc000 for user code.'
    当使用模块级别的re.sub()函数时。模式作为第一个參数传入。模式能够为一个对象或者字符串;假设你须要指定正則表達式标志,你必须使用一个模式对象作为第一个參数,或者在模式字符串中用嵌入的修饰语,比如:sub("(?i)b+", "x", "bbbb BBBB")返回'x x'。

    常见问题

    正則表達式在一些应用中是实用的工具,但他们的行为不是直观的。有时并不依照你所期望的方式工作。这节将描写叙述一些常见的陷阱。

    用String方法

    有时使用re模块是错误的。假设你正在匹配一个固定的字符串,或者一个单个的字符类。而且你并没实用到不论什么re特征,比如IGNORECASE标志,那么正則表達式的威力并不被须要。String有几个为固定的字符串运行操作的方法,而且它们通常更快,由于他们的实现是单个的C循环,而且针对该场景做了优化。


    一个常见的样例是替换一个固定的字符串为还有一个。比如,你想替换word为deed,re.sub()似乎能够用于这样的场景,可是你应该考虑replace()方法。注意replace()也将替换单词内的word,比如改动swordfish为sdeedfish,可是简单的RE word也将做那。(为了避免单词内的替换,模式将必须是\bword\b,为了要求word是一个独立的单词。这一点超出了replace()的能力。)
    还有一个常见任务是探測字符串中某个字符的位置,或者使用还有一个字符替换它。

    你能够使用类似这种操作来实现:re.sub('\n', ' ', S)。可是translate()也能够完毕这种任务。而且比不论什么正則表達式的操作都更快。


    总之。使用re模块之前,考虑你的问题能否使用更快、更简单的字符串方法解决。

    match() VS search()

    match()函数仅检查RE是否在字符串的開始匹配。而search()将扫描整个字符串。记住这一点很重要,match()将仅报告在起点为0进行的成功匹配;假设匹配的起点不为0,match()将不报告它。
    >>> print(re.match('super', 'superstition').span())
    (0, 5)
    >>> print(re.match('super', 'insuperable'))
    None
    还有一个方面,search()将扫描整个字符串,报告发现的第一个成功匹配。
    >>> print(re.search('super', 'superstition').span())
    (0, 5)
    >>> print(re.search('super', 'insuperable').span())
    (2, 7)
    有时你会被引诱使用re.match(),只添加.*到你的RE之前。你应该拒绝这个诱惑。转而使用re.search()。正則表達式编译器会做一些RE的分析。为了加速查找匹配的处理。一个如此的分析是分析出匹配的首字符必然是什么。比如。一个以Crow開始的模式必须匹配首字符'C'。这个分析使引擎高速扫描字符串查询開始字符,当'C'被发现时才继续向下匹配。


    添加.*将使这个优化无效,要求扫描到字符串的结尾。在回溯发现RE其余部分的一个匹配。因此,优先使用re.search()。

    贪婪 VS 非贪婪

    当反复一个正則表達式时。比如a*。正則表達式的行为是尽可能多的匹配。

    这一点常常会导致一些问题,当你尝试匹配一对对称的限定符时,比如包括HTML标签的尖括号,因为.*的贪婪特性,简单的匹配一个HTML标签的模式不工作:

    >>> s = '<html><head><title>Title</title>'
    >>> len(s)
    32
    >>> print(re.match('<.*>', s).span())
    (0, 32)
    >>> print(re.match('<.*>', s).group())
    <html><head><title>Title</title>
    RE在<html>中匹配'<'。然后.*消费字符串其余的全部部分,因为RE最后的>不能匹配。于是正則表達式引擎不得不回溯字符直到它为>找到一个匹配。

    最后的匹配就是从<html>的'<'到</title>的'>'。并非你想要的。
    在这样的场景,应该使用非贪婪限制符*?、+?、?

    ?

    、或者{m,n}?,他们将匹配尽可能少的字符。

    在上面的样例中,在第一个'<'匹配之后,'>'将被马上尝试,假设失败,引擎每次前进一个字符,再次尝试。最后得到正确的结果:

    >>> print(re.match('<.*?>', s).group())
    <html>
    (注意使用正則表達式解析HTML或者XML是痛苦的。由于写一个能处理全部场景的正則表達式是很复杂的,使用HTML或者XML解析器来完毕这种任务。)

    使用re.VERBOSE

    到如今你可能注意到正則表達式是一个很紧凑的形式,可是他们不是很易读的。中等复杂程度的RE能成为反斜杠、括号和元字符的冗长的集合,使他们呢难于阅读和理解。


    为如此的RE,当编译正則表達式时指定re.VERBOSE标志是有帮助的。由于它同意你格式化正則表達式使其更清晰。


    re.VERBOSE标志有几个影响。在正則表達式中但不在字符类中的空格将被忽略。这意味着一个表达式比如dog | cat将等价于dog|cat,可是[a b]任然匹配字符'a'、'b'和空格。此外,你也能放凝视在一个RE中。凝视从一个#字符到下一行。当用三引號字符串时。RE被格式化的更加清晰:

    pat = re.compile(r"""
     \s*                 # Skip leading whitespace
     (?P<header>[^:]+)   # Header name
     \s* :               # Whitespace, and a colon
     (?P<value>.*?)      # The header's value -- *?

    used to # lose the following trailing whitespace \s*$ # Trailing whitespace to end-of-line """, re.VERBOSE)

    和以下的表达式比起来。这是更可读的:
    pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?

    P<value>.*?)\s*$")


    转载于:https://www.cnblogs.com/gavanwanggw/p/6803049.html

    展开全文
  • 如果待处理任务满足: ...假设待处理的任务为:有很多文件目录,对于每个文件目录,搜索匹配一个给定字符串的文件的所有行(相当于是实现grep的功能)。 则此处子任务为:给定一个目录,搜索匹配一个给定字符串...
  • 为什么re.match匹配不到?re.match匹配规则怎样?(捕一下seo) ...网上的定义【从要匹配字符串的头部开始,当匹配到string的尾部还没有匹配结束时,返回None;当匹配过程中出现了无法匹配的字母,返回None...
  • Python爬虫入门——正则表达式

    千次阅读 2016-08-25 14:37:45
    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。 正则表达式是用来匹配
  • 正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容就易如反掌了。 正则表达式的大致匹配...
  • 上面第一个正则表达式模式是“foo”,该模式没有使用任何特殊字符进行匹配其他字符,所以能够匹配此模式的只有foo字符串,同理,Python和abc123一样。 特殊符号与字符 常见的正则表达式的特殊符号与字符: ...
  • python中的r

    2020-04-11 13:56:00
    形如r"c:\news",由r开头引起的字符串就是声明了后面引号里的东西是原始字符串, 在里面放任何字符都表示该字符的原始含义。 有时候匹配正则表达式中,有时候会有斜线 \ 没有 r ,就要写2个 \ \ 有 r ,只要写一...
  • 正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python 同样不例外。正则表达式的大致匹配过程是: 依次拿出表达式和文本中的字符比较, 如果每一个字符都能匹配,则匹配成功...
  • python之正则表达式

    2016-07-30 11:12:00
    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,正则表达式使用耽搁字符串来描述,匹配一系列符合某个句法规则的字符串。 简单理解,就是对字符串的检索匹配和...
  • 1. 提取数据 在前面我们已经搞定了怎样...正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容
  • python java编程语言间来回切换,...Python根据正则表达式提供两种不同的基本操作:match只在字符串的开始确认一个匹配,而search在字符串的任何匹配的位置都确认. Regular expressions module of Python is ca...
  • 同时正则表达式很难掌握。...在直接使用字符串表示的正则表达式进行search,match和findall操作时,python会将字符串转换为正则表达式对象。而使用compile完成一次转换之后,在每次使用模式的时候就不用重复转换。当然,
  • Python爬虫基础4

    2018-01-26 10:34:23
    正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。  正则表达式是用来匹配
  • 作为一门现代语言,正则表达式是必不可缺的,在Python中,正则表达式位于re模块。 1 import re 这里不说正则表达式怎样...例子一,字符串中是否包含数字: 1 import re 2 userinput = input("please input...
  • 1. 提取数据 在前面我们已经搞定了怎样...正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容
  • 在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则...正则表达式是用来匹配字符串非常强大的工具,在其他编程...
  •  正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。  正则表达式是用来匹配字符.....

空空如也

空空如也

1 2 3 4 5 6
收藏数 115
精华内容 46
关键字:

python怎样字符串匹配

python 订阅