正则表达式和python的re模块

时间:2022-12-04 22:35:39

正则表达式和python的re模块

 9月 4 2014 更新日期:9月 4 2014

文章目录
  1. 1. 什么是正则表达式
  2. 2. 元字符使用一览表:
  3. 3. 字符转义
  4. 4. 重复
  5. 5. 字符类
  6. 6. 分支条件
  7. 7. 分组
  8. 8. re模块
    1. 8.0.1. compile
    2. 8.0.2. match和search
    3. 8.0.3. split
    4. 8.0.4. findall
    5. 8.0.5. finditer
    6. 8.0.6. sub
    7. 8.0.7. subn
  • 9. 参考资料
  • 什么是正则表达式

    在编写处理字符串的程序或网页时,经常会有查找符合某些复杂规则的字符串的需要。正则表达式就是用于描述这些规则的工具。正则表达式就是记录文本规则的代码,换句话说,正则表达式是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。模式描述在搜索文本时要匹配的一个或多个字符串。

    字符是计算机软件处理文字时最基本的单位,可能是字母,数字,标点符号,空格,换行符,汉字等等。字符串是0个或更多个字符的序列。文本也就是文字,字符串。说某个字符串匹配某个正则表达式,通常是指这个字符串里有一部分(或几部分分别)能满足表达式给出的条件。

    假设你要在英文小说中查找Hi,那么使用的正则表达式就是Hi,这个很简单吧,不过,通常处理正则表达式的工具(例如后面会提到的python的re模块)会提供忽略大小写的选项。

    不幸的是,很多单词里都包含了很多单词里包含hi这两个连续的字符,比如him,history,high等等。用hi来查找的话,这里边的hi也会被找出来。如果要精确地查找hi这个单词的话,我们应该使用\bhi\b

    \b是正则表达式规定的一个特殊代码(这里称为元字符,metacharacter),代表着单词的开头或结尾,也就是单词的分界处。

    假设你要找的Hi后面不远处有一个ZH,那么可以用\bhi\b.*\bZH\b.

    这里,.是另一个元字符,匹配除了换行符以外的任意字符。*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配,例如,zo* 能匹配 "z" 以及 "zoo"等 。

    如果同时使用其它元字符,我们就能构造出功能更强大的正则表达式。比如下面这个例子:
    0\d\d-\d\d\d\d\d\d\d\d匹配这样的字符串:以0开头,然后是两个数字,然后是一个连字号“-”,最后是8个数字(也就是中国的电话号码。当然,这个例子只能匹配区号为3位的情形)。

    现在你已经知道几个很有用的元字符了,如\b,.,*,还有\d.正则表达式里还有更多的元字符,比如\s匹配任意的空白符,包括空格,制表符(Tab),换行符,中文全角空格等。\w匹配字母或数字或下划线或汉字等。

    下面来看看更多的例子:
    \ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。
    好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w。不错,这与学习英文时要背的成千上万个同名的东西的确关系不大 :)
    \d+匹配1个或更多连续的数字。这里的+是和*类似的元字符,不同的是*匹配重复任意次(可能是0次),而+则匹配重复1次或更多次。
    \b\w{6}\b 匹配刚好6个字符的单词。


    元字符使用一览表:

    上面介绍了部分的metacharacter,下面给出元字符一览表,使用时可以查找。

    字符

    说明

    \

    将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,“n”匹配字符“n”。“\n”匹配换行符。序列“\”匹配“\”,“(”匹配“(”。

    ^

    匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与“\n”或“\r”之后的位置匹配。

    $

    匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与“\n”或“\r”之前的位置匹配。

    零次或多次匹配前面的字符或子表达式。例如,zo 匹配“z”和“zoo”。 等效于 {0,}。

    +

    一次或多次匹配前面的字符或子表达式。例如,“zo+”与“zo”和“zoo”匹配,但与“z”不匹配。+ 等效于 {1,}。

    ?

    零次或一次匹配前面的字符或子表达式。例如,“do(es)?”匹配“do”或“does”中的“do”。? 等效于 {0,1}。

    {n}

    是非负整数。正好匹配 n 次。例如,“o{2}”与“Bob”中的“o”不匹配,但与“food”中的两个“o”匹配。

    {n,}

    是非负整数。至少匹配 次。例如,“o{2,}”不匹配“Bob”中的“o”,而匹配“foooood”中的所有 o。“o{1,}”等效于“o+”。“o{0,}”等效于“o ”。

    {n,m}

    M 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,“o{1,3}”匹配“fooooood”中的头三个 o。’o{0,1}’ 等效于 ‘o?’。注意:您不能将空格插入逗号和数字之间。

    ?

    当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是“非贪心的”。“非贪心的”模式匹配搜索到的、尽可能短的字符串,而默认的“贪心的”模式匹配搜索到的、尽可能长的字符串。例如,在字符串“oooo”中,“o+?”只匹配单个“o”,而“o+”匹配所有“o”。

    .

    匹配除“\n”之外的任何单个字符。若要匹配包括“\n”在内的任意字符,请使用诸如“[\s\S]”之类的模式。

    (pattern)

    匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果“匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用“(”或者“)”。

    (?:pattern)

    匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用“or”字符 (|) 组合模式部件的情况很有用。例如,’industr(?:y|ies) 是比 ‘industry|industries’ 更经济的表达式。

    (?=pattern)

    执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?=95|98|NT|2000)’ 匹配“Windows 2000”中的“Windows”,但不匹配“Windows 3.1”中的“Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。

    (?!pattern)

    执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?!95|98|NT|2000)’ 匹配“Windows 3.1”中的 “Windows”,但不匹配“Windows 2000”中的“Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。

    x|y

    匹配 x 或 y。例如,’z|food’ 匹配“z”或“food”。’(z|f)ood’ 匹配“zood”或“food”。

    [xyz]

    字符集。匹配包含的任一字符。例如,“[abc]”匹配“plain”中的“a”。

    [^xyz]

    反向字符集。匹配未包含的任何字符。例如,“[^abc]”匹配“plain”中的“p”。

    [a-z]

    字符范围。匹配指定范围内的任何字符。例如,“[a-z]”匹配“a”到“z”范围内的任何小写字母。

    [^a-z]

    反向范围字符。匹配不在指定的范围内的任何字符。例如,“[^a-z]”匹配任何不在“a”到“z”范围内的任何字符。

    \b

    匹配一个字边界,即字与空格间的位置。例如,“er\b”匹配“never”中的“er”,但不匹配“verb”中的“er”。

    \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。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 和 m 是八进制数字 (0-7)。

    \nml

    当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml

    \un

    匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。


    字符转义

    如果你想查找元字符本身的话,比如你查找.,或者,就出现了问题:你没办法指定它们,因为它们会被解释成别的意思。这时你就得使用\来取消这些字符的特殊意义。因此,你应该使用.和\。当然,要查找\本身,你也得用\.

    例如:deerchao\.net匹配deerchao.netC:\\Windows匹配C:\Windows


    重复

    正则表达式第一件能做的事是能够匹配不定长的字符集,而这是其它能作用在字符串上的方法所不能做到的。 不过,如果那是正则表达式唯一的附加功能的话,那么它们也就不那么优秀了。它们的另一个功能就是你可以指定正则表达式的一部分的重复次数。

    就像前面介绍的元字符 ** 并不匹配字母字符 “*”;相反,它指定前一个字符可以被匹配零次或更多次,而不是只有一次。

    上面我们的元字符表把大部分元字符都说了,这里我们抽取出重复限定符。

    代码/语法 说明
    * 重复零次或更多次
    + 重复一次或更多次
    ? 重复零次或一次
    {n} 重复n次
    {n,} 重复n次或更多次
    {n,m} 重复n到m次

    再举个例子,ca?t 将匹配 “ct” (0 个 “a” 字符) 或 “cat” (1 个 “a”);
    有了上面的这些限定元字符,可以很好得处理重复情况,只要运用得当。


    字符类

    要想查找数字,字母或数字,空白是很简单的,因为已经有了对应这些字符集合的元字符,但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办?

    很简单,你只需要在方括号里列出它们就行了,像[aeiou]就匹配任何一个英文元音字母,[.?!]匹配标点符号(.或?或!)。

    我们也可以轻松地指定一个字符范围,像[0-9]代表的含意与\d就是完全一致的:一位数字;同理[a-z0-9A-Z_]也完全等同于\w(如果只考虑英文的话)。

    下面是一个更复杂的表达式:(?0\d{2}[) -]?\d{8}。

    “(”和“)”也是元字符,后面的分组节里会提到,所以在这里需要使用转义。

    这个表达式可以匹配几种格式的电话号码,像(010)88886666,或022-22334455,或02912345678等。我们对它进行一些分析吧:首先是一个转义字符(,它能出现0次或1次(?),然后是一个0,后面跟着2个数字(\d{2}),然后是)或-或空格中的一个,它出现1次或不出现(?),最后是8个数字(\d{8})。


    分支条件

    不幸的是,刚才那个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:

    0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。

    \(?0\d{2}\)?[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。

    \d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。


    分组

    我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

    (\d{1,3}.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

    IP地址中每个数字都不能大于255. 经常有人问我, 01.02.03.04 这样前面带有0的数字, 是不是正确的IP地址呢? 答案是: 是的, IP 地址里的数字可以包含有前导 0 (leading zeroes).

    不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。

    理解这个表达式的关键是理解2[0-4]\d|25[0-5]|[01]?\d\d?,这里我就不细说了,你自己应该能分析得出来它的意义。


    re模块

    Python中得正则表达式(regular expression)模块,即re模块,功能还是很强大的。在介绍re之前,先看下面这部分。

    正则表达式使用反斜杠” \ “来代表特殊形式或用作转义字符,这里跟Python的语法冲突,因此,Python用” \\ “表示正则表达式中的” \ “,因为正则表达式中如果要匹配” \ “,需要用\来转义,变成” \ “,而Python语法中又需要对字符串中每一个\进行转义,所以就变成了” \\ “。

    上面的写法是不是觉得很麻烦,为了使正则表达式具有更好的可读性,Python特别设计了原始字符串(raw string),需要提醒你的是,在写文件路径的时候就不要使用raw string了,这里存在陷阱。raw string就是用’r’作为字符串的前缀,如 r”\n”:表示两个字符”\”和”n”,而不是换行符了。Python中写正则表达式时推荐使用这种形式。

    下面来看一下re模块的几个函数:

    compile

    re.compile(strPattern[, flag]):

    这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。
    第二个参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。
    另外,你也可以在regex字符串中指定模式,
    比如re.compile(‘pattern’, re.I | re.M)与re.compile(‘(?im)pattern’)是等价的。
    可选值有:


    re.I(IGNORECASE): 忽略大小写(括号内是完整写法,下同)
    re.M(MULTILINE): 多行模式,改变'^'和'$'的行为(参见上图)
    re.S(DOTALL): 点任意匹配模式,改变'.'的行为
    re.L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
    re.U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
    re.X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。


    其实compile方法的作用,不是很明显,因为下面的两个得到的结果等价。调用compile后,返回RegexObject对象,可以用该对象调用macth()和search()等匹配方法。

    import repattern = r"hi";string = "hi,jack";prog = re.compile(pattern);result1 = prog.match(string);print "result1: ", result1.group();result2 = re.match(pattern, string);print "result2: ",result2.group();

    输出的结果是一样的。这里match下面会介绍,它是一个匹配的方法,group方法后面也会介绍,它这里输出的时匹配的内容。可以试验一下,就明白他两是等效的。

    match和search

    两个方法都是进行匹配时调用的。但他们有不同之处。

    Python提供了两种不同的原始操作:match和search。match是从字符串的起点开始做匹配,而search(perl默认)是从字符串做任意匹配。

    例子1

    import reresult1 = re.match("c","abcde");if(result1):    print("result1:"+result1.group());else:    print("nothing");result2 = re.search("c","abcde");if(result2):    print("result2:"+result2.group());

    输出:

    nothing

    result2:c

    例子2

    import reresult1 = re.match("a","abcde");if(result1):    print("result1:"+result1.group());else:    print("nothing");result2 = re.search("a","abcde");if(result2):    print("result2:"+result2.group());

    输出:

    result1:a

    result2:a

    例子3

    match函数可以设置匹配开始的位置,下面分别从0,1,2位置开始匹配。当然也可以设置终止的位置,具体可以查API文档。

    import repattern = re.compile("c");result1 = pattern.match("abcde",0);if(result1):    print("result1:"+result1.group());else:    print("result1: nothing");result2 = pattern.match("abcde",1);if(result2):    print("result2:"+result2.group());else:    print("result2: nothing");result3 = pattern.match("abcde",2);if(result3):    print("result3:"+result3.group());else:    print("result3: nothing");

    输出:

    result1: nothing

    result2: nothing

    result3:c

    split

    re.split(pattern, string, maxsplit=0)

    通过正则表达式将字符串分离。如果用括号将正则表达式括起来,那么匹配的字符串也会被列入到list中返回。maxsplit是分离的次数,maxsplit=1分离一次,默认为0,不限制次数。

    看一下例子:

    import re    print re.split('\W+', 'Words, words, words.')

    输出:

    [‘Words’, ‘words’, ‘words’, ‘’]

    思考一下为什么输出会是这个? 查看一下\W的作用,注意W是大写的。

    findall

    re.findall(pattern, string, flags=0)

    找到 RE 匹配的所有子串,并把它们作为一个列表返回。这个匹配是从左到右有序地返回。如果无匹配,返回空列表。

    import reprint re.findall("\d","1a2b3c4d");

    输出:

    [‘1’, ‘2’, ‘3’, ‘4’]

    finditer

    re.finditer(pattern, string, flags=0)

    找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。这个匹配是从左到右有序地返回。如果无匹配,返回空列表。

    import reit = re.finditer(r"\d+","123abc456efg789hij")for match in it:    print match.group()

    输出:

    123
    456
    789

    sub

    sub(pattern, repl, string, count=0, flags=0)

    其用途是用来替换匹配成功的字串,被替换成repl。值得一提的时,这里的repl不仅仅可以是字符串,也可以是方法。

    首先看下字符串的时候,被匹配的字符串就会被替换成为repl。

    import reprint re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)

    输出:

    Baked Beans & Spam

    可以使用\id或\g<id>、\g<name>引用分组.

    当repl是方法的时候。这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。

    import redef dashrepl(matchobj):    if matchobj.group(0) == '-': return ' '    else: return '-'print  re.sub('-{1,2}', dashrepl, 'pro----gram-files')print  re.sub('-{1,2}', dashrepl, 'pro----gram-files')

    输出:

    pro—gram files

    subn

    subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]):
    多返回 (sub(repl, string[, count]), 替换次数)。

    import reprint re.subn(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)


    参考资料

    1. 正则表达式30分钟入门教程 (推荐)
    2. 正则表达式语法
    3. Python-re模块
    4. 深入浅出之正则表达式 (推荐)
    5. 正则表达式re模块详解 (推荐)
    6. Python正则表达式操作指南 (推荐)
    7. python正则表达式教程