# 12-Python正则表达 **正则表达式** wing忠告:搞运维的童鞋如果没有接触过shell里面的正则表达式,建议先跳过本章 **导语** 正则表达式是一个特殊的字符序列,用来检查一个字符串是否与某种模式匹配。 Python提供的是 Perl 风格的正则表达式模式。 re 模块使 Python拥有全部的正则表达式功能。 compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。 re 模块也提供与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。 **正则表达式的使用有两种方式:** 1.使用正则之前先编译正则 2.使用正则之前不编译正则 **1.使用正则之前先编译正则** 正则表达式被编译成 'RegexObject' 实例,可以为不同的操作提供方法,如模式匹配搜索或 字符串替换。 \>>> import re \>>> p=re.compile('n+') #先编译正则表达式,生成一个表达式对象 \>>> p.match('nnnning').group() #再使用表达式去进行匹配 'nnnn' group()返回被 RE 匹配的字符串 //'nnnn' start()返回匹配开始的位置 //0 end()返回匹配结束位置的下一个位置 //4 span()返回一个元组包含匹配 (开始,结束) 的位置 //(0,4) 不包含下边界 **2.使用正则之前不编译正则** 利用match和search帮我们编译 **re.match():** re.match 尝试从字符串的起始位置匹配一个模式,如果不是在起始位置匹配成功,match() 就返回none。 语法: re.match(pattern, string, flags=0) 参数说明: | 参数 | 描述 | | ------- | ------------------------------------------------------------ | | pattern | 匹配的正则表达式 | | string | 要匹配的字符串 | | flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等 | 匹配成功re.match方法返回一个匹配的对象,否则返回None。 使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。 | 匹配对象方法 | 描述 | | ------------ | ------------------------------------------------------------ | | group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 | | groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。 | 例 1: \#!/usr/bin/python3 import re print(re.match('www', 'www.fklinux.com').span()) # 在起始位置匹配 print(re.match('com', 'www.fklinux.com')) ​ 输出结果: ​ (0, 3) ​ None 例 2: \#!/usr/bin/python import re line = "Cats are smarter than dogs" matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I) \#上面的re.M|re.I是正则表达式修饰符 - 可选标志,后面马上会讲到 ​ if matchObj: ​ print "matchObj.group() : ", matchObj.group() ​ print "matchObj.group(1) : ", matchObj.group(1) ​ print "matchObj.group(2) : ", matchObj.group(2) ​ else: ​ print "No match!!" ​ 执行结果: ​ matchObj.group() : Cats are smarter than dogs ​ matchObj.group(1) : Cats ​ matchObj.group(2) : smarter **re.search():** re.search 扫描整个字符串并返回第一个成功的匹配。 函数语法: re.search(pattern, string, flags=0) 函数参数说明: | 参数 | 描述 | | ------- | ------------------------------------------------------------ | | pattern | 匹配的正则表达式 | | string | 要匹配的字符串。 | | flags | 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。 | 匹配成功re.search方法返回一个匹配的对象,否则返回None。 使用group(num) 或 groups() 匹配对象函数来获取匹配表达式。 | 匹配对象方法 | 描述 | | ------------ | ------------------------------------------------------------ | | group(num=0) | 匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 | | groups() | 返回一个包含所有小组字符串的元组 | 例 1: \>>> print(re.search('www', 'www.fklinux.com')) <_sre.SRE_Match object at 0x7fbbe5816c60> #python2只显示内存地址 <_sre.SRE_Match object; span=(0, 3), match='www'> #python3的显示结果 ​ \>>> print(re.search('com', 'www.fklinux.com')) ​ <_sre.SRE_Match object at 0x7fbbe5816c60> #python2只显示内存地址 ​ <_sre.SRE_Match object; span=(12, 15), match='com'> #python3的显示结果 ​ \>>> print(re.search('www', 'www.fklinux.com').span()) #加上span()方法处理一下 ​ (0, 3) ​ \>>> print(re.search('com', 'www.fklinux.com').span()) ​ (12, 15) 例 2: \#!/usr/bin/python import re line = "Cats are smarter than dogs"; searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I) #每个小括号一个分组 ​ if searchObj: ​ print "searchObj.group() : ", searchObj.group() ​ print "searchObj.group(1) : ", searchObj.group(1) ​ print "searchObj.group(2) : ", searchObj.group(2) ​ else: ​ print "Nothing found!!" ​ 执行结果: ​ searchObj.group() : Cats are smarter than dogs # ​ searchObj.group(1) : Cats ​ searchObj.group(2) : smarter 例3: \>>> print line wig wing winng winnng winnnng \>>> re.search('(wig)( +)(wing)',line).groups() #所有匹配的组的内容 ('wig', ' ', 'wing') \>>> re.search('(wig)( +)(wing)',line).group() #所有匹配的组的内容 'wig wing' \>>> re.search('(wig)( +)(wing)',line).group(0) #所有匹配的组的内容 'wig wing' \>>> re.search('(wig)( +)(wing)',line).group(1) #第一组匹配的内容 'wig' \>>> re.search('(wig)( +)(wing)',line).group(2) #第二组匹配的内容 ' ' \>>> re.search('(wig)( +)(wing)',line).group(3) #第三组匹配的内容 'wing' **re.match与re.search的区别** re.match 只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None。 re.search 匹配整个字符串,直到找到一个匹配。 例: \#!/usr/bin/python import re line = "Cats are smarter than dogs" matchObj = re.match( r'dogs', line, re.M|re.I) if matchObj: print "match --> matchObj.group() : ", matchObj.group() else: print "No match!!" ​ matchObj = re.search( r'dogs', line, re.M|re.I) ​ if matchObj: ​ print "search --> matchObj.group() : ", matchObj.group() ​ else: ​ print "No match!!" ​ 运行结果: ​ No match!! ​ search --> matchObj.group() : dogs **正则表达式修饰符 - 可选标志** 正则表达式可以包含一些可选标志修饰符来控制匹配的模式 修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。 如: re.I | re.M 被设置成 I 和 M 标志 | 修饰符 | 描述 | | ------ | ------------------------------------------------------------ | | re.I | 使匹配对大小写不敏感 | | re.L | 做本地化识别(locale-aware)匹配 | | re.M | 多行匹配,影响 ^ 和 $ | | re.S | 使 . 匹配包括换行在内的所有字符 | | re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. | | re.X | 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。 | ​ [root@wing python]# cat a.py ​ \#!/usr/bin/env python ​ import re ​ line='''winGa ​ winng ​ winnnga''' ​ r=re.compile(r'g.') #这里的点不包括换行,所以匹配到的是ga ​ print r.search(line).group() ​ [root@wing python]# python a.py ​ ga ​ \------------------------------------------------ ​ [root@wing python]# cat a.py ​ \#!/usr/bin/env python ​ import re ​ line='''winGa ​ winng ​ winnnga''' ​ r=re.compile(r'g.',re.S) #这里的点包括换行,所以匹配到的是g和换行 ​ print r.search(line).group() ​ [root@wing python]# python a.py ​ g ​ [root@wing python]# ​ \----------------------------------------------- ​ [root@wing python]# cat a.py ​ \#!/usr/bin/env python ​ \#coding=utf-8 ​ import re ​ line='''winGa ​ winng ​ winnnga''' ​ r=re.compile(r'g. #hello,这是注释',re.X) #用了re.X后可以给正则表达式添加注释 ​ print r.search(line).group() ​ [root@wing python]# python a.py ​ ga ====================================================== **findall():** findall()和 search()相似之处: 在于二者都执行字符串搜索 findall()和 match()与search()不同之处: findall()总返回一个列表(match和search返回的是元组) 如果 findall()没有找到匹配的部分,会返回空列表 如果成功找到匹配部分,则返回所有匹配部分的列表(按从左到右出现的顺序排列) 语法: findall(rule , target [,flag] ) rule 规则 target 目标字符串 flag 规则选项(选项功能和compile和search的flag一样)。返回结果是一个列表, 中间存放的是符合规则的字符串。如果没有符合规则的字符串被找到,就返回一个空列表。 例子: \>>> re.findall('car', 'car') ['car'] \>>> re.findall('car', 'scary') ['car'] \>>> re.findall('car', 'carry the barcardi to the car') ['car', 'car', 'car'] ====================================================== **检索和替换** Python 的re模块提供了re.sub和re.subn用于替换字符串中的匹配项。 ### re.sub() : 语法: re.sub(pattern, repl, string, max=0) 返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式没有发现,字符将被没有改变地返回。 可选参数 count 是模式匹配后替换的最大次数;count 必须是非负整数。缺省值是 0 表示替换所有的匹配。 例: \#!/usr/bin/python import re phone = "2004-959-559 # This is Phone Number" \# Delete Python-style comments num = re.sub(r'#.*$', "", phone) print "Phone Num : ", num \# Remove anything other than digits num = re.sub(r'\D', "", phone) print "Phone Num : ", num ​ 执行结果: ​ Phone Num : 2004-959-559 ​ Phone Num : 2004959559 \-------------------------------------------- **re.subn():** subn()和 sub()一样,但它还返回一个表示替换次数的数字,替换后的字符串和表示替换次数的数字作为一个元组的元素返回。 ​ \>>> re.sub('[ae]', 'X', 'abcdef') ​ 'XbcdXf' ​ \>>> re.subn('[ae]', 'X', 'abcdef') ​ ('XbcdXf', 2) ================================================== ## re.split(): re 模块和正则表达式对象的方法 split()与字符串的 split()方法相似,前者是根据正则表达式模式分隔字符串,后者是根据固定的字符串分割,因此与后者相比,显著提升了字符分割的能力。如果你不想在每个模式匹配的地方都分割字符串,你可以通过设定一个值参数(非零)来指定分割的最大次数。 如果分隔符没有使用由特殊符号表示的正则表达式来匹配多个模式,那re.split()和string.split()的执行过程是一样的: 比如:在每一个冒号处分隔 \>>> re.split(':', 'str1:str2:str3') ['str1', 'str2', 'str3'] 使用正则表达式做分割符: \>>> line='who ----> are ----> you' \>>> re.split(r'\s*-+>\s*',line) ['who', 'are', 'you'] ================================================== **正则表达式模式** 模式字符串使用特殊的语法来表示一个正则表达式: • 字母和数字表示他们自身 • 多数字母和数字前加一个反斜杠时会拥有不同的含义 • 标点符号只有被转义时才匹配自身,否则它们表示特殊的含义 • 反斜杠本身需要使用反斜杠转义 • 由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素(如 r'\t',等价于'\\t')匹配相应的特殊字符。 下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。 | 模式 | 描述 | | ----------- | ------------------------------------------------------------ | | ^ | 匹配字符串的开头 | | $ | 匹配字符串的末尾。 | | . | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 | | [...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m'或'k' | | [^...] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 | | * | 匹配0个或多个的表达式。 | | + | 匹配1个或多个的表达式。 | | ? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 | | {n} | 精确匹配n个前面表达式。 | | {n,} | 匹配最少n个前面表达式。 | | {n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 | | a\|b | 匹配a或b | | (re) | 既匹配括号内的表达式,也表示一个组 | | (?: re) | 类似 (...), 但是不表示一个组 | | (?#...) | 注释. | | \w | 匹配字母数字及下划线 | | \W | 匹配非字母数字及下划线 | | \s | 匹配任意空白字符,等价于 [\t\n\r\f]. | | \S | 匹配任意非空字符 | | \d | 匹配任意数字,等价于 [0-9]. | | \D | 匹配任意非数字 | | \A | 匹配字符串开始,类似于^,但他主要是为了那些没有caret(^)字符的键盘准备的 | | \Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。类似于$,但他主要是为了那些没有($)字符的键盘准备的 | | \b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。 | | \B | 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。 | | \n, \t, 等. | 匹配一个换行符。匹配一个制表符。等 | | \1...\9 | 匹配第n个分组的子表达式。 | | \10 | 匹配第n个分组的子表达式,如果它经匹配。否则指的是八进制字符码的表达式。 | 注意: 1.上面有些正则如果不管用,就说明必须用原始字符串,比如'er\b'必须写成r'er\b'才会生效 原因是 ASCII 字符和正则表达式特殊字符间所产生的冲突。比如,特殊符号“"b”"ASCII 字符中代表退格键,但同时"\b"也是一个正则表达式的特殊符号, 代表"匹配一个单词边界"。为了让 RE 编译器把两个字符"\b"当成你想要表达的字符串,而不是一个退格键,你需要用另一个反斜线对它进行转义,即可以这样写:"\\b"或者r"\b"。 2.‘*?’ ‘+?’ ‘??’ 最小匹配: ‘*’ ‘+’ ‘?’ 通常都是尽可能多的匹配字符。有时候我们希望它尽可能少的匹配。比如一个 c 语言的注释 ‘/* part 1 */ /* part 2 */’ ,如果使用最大规则: 问号'?'有两种含义: 1)单独使用时表示匹配出现零次或一次的情况 2)紧跟在表示重复的元字符后面时,表示要求搜索引擎匹配的字符串越短越好。例如:(+?),原本+表示1次或多次,现在只表示1次 例: \>>> re.sub(r'n+','A','wing winnng is a good student') 'wiAg wiAg is a good studeAt' \>>> re.sub(r'n+?','A','wing winnng is a good student') 'wiAg wiAAAg is a good studeAt' 3.‘(?:)’ 无捕获组 当你要将一部分规则作为一个整体对它进行某些操作,比如指定其重复次数时,你需要将这部分规则用 ’(?:’ ‘)’ 把它包围起来,而不能仅仅只用一对括号,那样将得到绝对出人意料的结果。 例:匹配字符串中重复的 ’ab’ \>>> s=’ababab abbabb aabaab’ \>>> re.findall( r’/b(?:ab)+/b’ , s ) ['ababab'] 如果仅使用一对括号,看看会是什么结果: \>>> re.findall( r’/b(ab)+/b’ , s ) ['ab'] 4.‘(?# )’ 注释 Python 允许你在正则表达式中写入注释,在 ’(?#'')’ 之间的内容将被忽略。 \>>> s=’ababab abbabb aabaab’ \>>> re.findall('abab(?#注释)ab',s) ['ababab'] 5.(?iLmsux) 编译选项指定 Python 的正则式可以指定一些选项,这个选项可以写在 findall 或 compile 的参数中,也可以写在正则式里,成为正则式的一部分。这在某些情况下会便利一些。具体的选项含义请看后面的 compile 函数的说明。 此处编译选项 ’i’ 等价于 IGNORECASE ,L 等价于 LOCAL ,m 等价于 MULTILINE , s 等价于 DOTALL , u 等价于 UNICODE , x 等价于 VERBOSE 。 请注意它们的大小写。在使用时可以只指定一部分,比如只指定忽略大小写,可写为 ‘(?i)’ ,要同时忽略大小写并使用多行模式,可以写为 ‘(?im)’ 。 另外要注意选项的有效范围是整条规则,即写在规则的任何地方,选项都会对全部整条正则式有效。 \>>> s=’ababab abbabb aabaab’ \>>> re.findall('abAb(?#注释)**ab(?i)**',s) ['ababab'] 6.前向界定与后向界定 有时候需要匹配一个跟在特定内容后面的或者在特定内容前面的字符串, Python 提供一个简便的前向界定和后向界定功能,或者叫前导指定和跟从指定功能。它们是: ‘(?<=…)’ 前向界定,括号中 ’…’ 代表你希望匹配的字符串的前面应该出现的字符串。 ‘(?=…)’ 后向界定,括号中的 ’…’ 代表你希望匹配的字符串后面应该出现的字符串。 例: 你希望找出 c 语言的注释中的内容,它们是包含在 ’/*’ 和 ’*/’ 之间,不过你并不希望匹配的结果把 ’/*’ 和 ’*/’ 也包括进来,那么你可以这样用: \>>> s=r’/* comment 1 */ code /* comment 2 */’ \>>> re.findall( r’(?<=/\*).+?(?=\*/)’ , s ) [' comment 1 ', ' comment 2 '] 注意:这里我们仍然使用了最小匹配,以避免把整个字符串给匹配进去了。 要注意的是,前向界定括号中的表达式必须是常值,即你不可以在前向界定的括号里写正则式。比如你如果在下面的字符串中想找到被字母夹在中间的数字,你不可以用前向界定: 例: \>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘ \>>> re.findall( r’(?<=[a-z]+)\d+(?=[a-z]+)' , s ) # 错误的用法 它会给出一个错误信息: error: look-behind requires fixed-width pattern 不过如果你只要找出后面接着有字母的数字,你可以在后向界定写正则式: \>>> re.findall( r’\d+(?=[a-z]+)’, s ) ['111', '333'] 如果你一定要匹配包夹在字母中间的数字,你可以使用组( group )的方式 \>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s ) ['111'] 除了前向界定前向界定和后向界定外,还有前向非界定和后向非界定,它的写法为: ‘(?>> re.findall( r’\d+(?!\w+)’ , s ) ['222'] ​ 注意这里我们使用了 \w 而不是像上面那样用 [a-z] ,因为如果这样写的话,结果会是: ​ \>>> re.findall( r’\d+(?![a-z]+)’ , s ) ​ ['11', '222', '33'] ​ 这和我们期望的似乎有点不一样。它的原因,是因为 ’111’ 和 ’222’ 中的前两个数字也是满足这个要求的。 ============================================= **正则表达式实例** 字符匹配: | 实例 | 描述 | | ------ | -------------- | | python | 匹配 "python". | 字符类: | 实例 | 描述 | | ----------- | --------------------------------- | | [Pp]ython | 匹配 "Python" 或 "python" | | rub[ye] | 匹配 "ruby" 或 "rube" | | [aeiou] | 匹配中括号内的任意一个字母 | | [0-9] | 匹配任何数字。类似于 [0123456789] | | [a-z] | 匹配任何小写字母 | | [A-Z] | 匹配任何大写字母 | | [a-zA-Z0-9] | 匹配任何字母及数字 | | [^aeiou] | 除了aeiou字母以外的所有字符 | | [^0-9] | 匹配除了数字外的字符 | 特殊字符类: | 实例 | 描述 | | ---- | ------------------------------------------------------------ | | . | 匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。 | | \d | 匹配一个数字字符。等价于 [0-9]。 | | \D | 匹配一个非数字字符。等价于 [^0-9]。 | | \s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 | | \S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 | | \w | 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。 | | \W | 匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'。 |