Python面试题之Python正则表达式re模块
一、Python正则表达式re模块简介
正则表达式,是一门相对通用的语言。简单说就是:用一系列的规则语法,去匹配,查找,替换等操作字符串,以达到对应的目的;此套规则,就是所谓的正则表达式。各个语言都有各自正则表达式的内置模块,包括Linux系统中sed、awk也都是使用正则表达式。当然Python中也有对正则表达式的支持,对应的就是Python内置的re模块。
Python的re模块(Regular Expression,正则表达式)提供各种正则表达式的匹配操作,使用这一内嵌于Python的语言工具,尽管不能满足所有复杂的匹配情况,但足够在绝大多数情况下能够有效地实现对复杂字符串的分析并提取出相关信息。Python会将正则表达式转化为字节码,利用C语言的匹配引擎进行深度优先的匹配。
二、正则表达式(Regexp)
正则表达式是由普通字符(例如字符a到z)以及特殊字符(称为”元字符”)组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
- 普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
- 特殊字符
所谓特殊字符,就是一些有特殊含义的字符,如tes*t中的*,简单的说就是表示任何字符串的意思。如果要查找字符串中的*符号,则需要对*进行转义,即在其前加一个\,如tes\*t匹配tes*t。许多元字符要求在试图匹配它们时特别对待,若要匹配这些特殊字符,必须首先使字符”转义”,即,将反斜杠字符\放在它们前面。
- 限定符
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。常用有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 等等。
- 定位符
定位符用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,\b描述单词的前或后边界,\B表示非单词边界。
下面表格列出了常用的正则表达式符号并给出了说明:
包含’ \ ’的特殊序列的意义如下表:
特殊表达式序列 | 意义 |
\n | 匹配一个换行符,等价于\x0a和\cJ。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,’er\b’可以匹配”never”中的’er’,但不能匹配”verb”中的’er’。 |
\B | 匹配非单词边界,’er\B’能匹配”verb”中的’er’,但不能匹配”never”中的’er’。 |
\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_]。 |
\r | 匹配一个回车符,等价于\x0d和\cM。 |
三、Python re使用
Python的re正则表达式模块定义了一系列函数,常量以及异常;同时,正则表达式被编译成‘ RegexObject ’实例,本身可以为不同的操作提供方法。接下来简要介绍一下这些函数的功能和用法。
1. compile
re.compile(pattern[, flags])
把正则表达式的模式和标识转化成正则表达式对象,就是把规则编译好,供match()和search()这两个函数使用。
re所定义的flag包括:
re.I:忽略大小写。
re.L:表示特殊字符集\w, \W, \b, \B, \s, \S依赖于当前环境。
re.M:多行模式。
re.S:即为’ . ’并且包括换行符在内的任意字符(’ . ’不包括换行符)。
re.U:表示特殊字符集\w, \W, \b, \B, \d, \D, \s, \S依赖于Unicode字符属性数据库。
re.X:为了增加可读性,忽略空格和’ # ’后面的注释。
例:以下两种用法结果相同,只是第一种编译过,重复使用效率更好;第二种是没有编译过的。
第一种:
>>> pattern = re.compile(r'^a') >>> pattern.match('abc') <_sre.SRE_Match object; span=(0, 1), match='a'>
当把正则表达式规则实例化之后,就会对应生成有很多实例属性与方法,如:match()、findall()、finditer()、split()、sub()、subn()、fullmatch()、search()、scanner()、flags、groupindex、groups、pattern等。
# 实例属性信息; >>> pattern.flags 32 >>> pattern.pattern '^a'
第二种
>>> re.match(r'^a', 'abc') <_sre.SRE_Match object; span=(0, 1), match='a'>
上面两种方式,任意一种如果匹配成功,则会返回一个对象,以及匹配范围和匹配到的值。接下来就可以把此对象实例化:
>>> pattern = re.compile(r'a') >>> data = pattern.match('abc abc')
对应也会生成很多实例属性和方法,如:end()、expand()、group()、groupdict()、groups()、span()、start()、endpos、pos、re、regs、string、lastgroup、lastindex等。
# 显示被匹配字符串; >>> data.string 'abc abc' # 显示匹配规则实例; >>> data.re re.compile('^a') # 显示匹配结果; >>> data.group() 'a' # 匹配结果在原字符串中的索引位置; >>> data.span() (0, 1) # 显示从什么索引位置开始匹配; >>> data.start() 0 # 显示匹配到什么索引位置结束; >>> data.end() 1
对于groups()方法,是把匹配结果以组的方式返回,是一个元祖;但有个条件就是正则表达式以组的形式匹配才行,如下:
>>> pattern = re.compile(r'a(\w*) a(\w*)') >>> data = pattern.match('abc abc') >>> data.group() 'abc abc' >>> data.groups() ('bc', 'bc')
对于groupdict()方法,是把匹配结果以dict方式显示;但正则匹配条件必须以组形式匹配,并且赋值一个key才行,如下:
>>> data = re.match(r'(?P<mail>[a-zA-Z0-9]{6,11}@163.com)', '12100231231@163.com') >>> data.groupdict() {'mail': '12100231231@163.com'}
更多属性与方法自己尝试
# 可以看到,我们在写规则时都在前面加了一个‘r’字符,是为了用来表明r”内的字符都无须转义,不然当我们包含一个’\n’时可能就会被转义为换行符,就无法做正确匹配操作了。
2. search
re.search(pattern, string[, flags])
在字符串中查找匹配正则表达式模式的位置,返回MatchObject的实例,如果没有找到匹配的位置,则返回None。
第一个参数:匹配规则
第二个参数:表示要匹配的字符串
第三个参数:标致位,用于控制正则表达式的匹配方式,比如上面介绍的大小写,多行匹配等
对于已编译的正则表达式对象来说(re.RegexObject),有方法:search (string[, pos[, endpos]])
若regex是已编译好的正则表达式对象,regex.search(string, 0, 50)等同于regex.search(string[:50], 0)。
具体示例如下:
>>> pattern = re.compile(r"a") # 匹配成功; >>> pattern.search("abcde") <_sre.SRE_Match object; span=(0, 1), match='a'> >>> pattern.search("bcade") <_sre.SRE_Match object; span=(2, 3), match='a'> # 未匹配成功; >>> pattern.search("abcde", 1) # \b界定符使用; >>> re.search(r'de\b', "abcde") <_sre.SRE_Match object; span=(3, 5), match='de'> >>> re.search(r'de\b', "abcdeef")
3. match
re.match(pattern, string[, flags])
尝试从字符串的起始位置尝试匹配一个正则表达式,也等于说是从第一个字符开始匹配。
第一个参数:匹配规则
第二个参数:表示要匹配的字符串
第三个参数:标致位,用于控制正则表达式的匹配方式,比如上面介绍的大小写,多行匹配等
>>> re.match('a','ab bc cd') #能匹配到; >>> re.match('b','ab bc cd') #不能匹配到;
对于已编译的正则表达式对象来说(re.RegexObject),有方法:match (string[, pos[, endpos]])
match()函数只在字符串的开始位置尝试匹配正则表达式,也就是只报告从位置0开始的匹配情况,而search()函数是扫描整个字符串来查找匹配。如果想要搜索整个字符串来寻找匹配,应当用search()。
案例一:匹配特殊符号
>>> re.match(r"\[[\w]\]", '[a]') <_sre.SRE_Match object; span=(0, 3), match='[a]'>
案例二:匹配IP地址
>>> ip = "172.16.10.1" >>> re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', ip) <_sre.SRE_Match object; span=(0, 11), match='172.16.10.1'>
案例三:匹配邮箱地址
>>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3}', '12100231231@163.com') <_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'> >>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.com$', '12100231231@163.com') <_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'>
案例三:分组匹配
>>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@163.com') >>> data.groups() ('163',) >>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@126.com') >>> data.groups() ('126',) >>> data = re.match(r'[a-zA-Z0-9]{11}@(.*),[\w]*@(.*)', '12100231231@163.com,abc@126.com') >>> data.group() '12100231231@163.com,abc@126.com' >>> data.groups() ('163.com', '126.com')
案例四:分组匹配并起别名
>>> data = re.match(r'(?P<name>\w*) "(?P<mail>[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3})" (?P<age>\d{1,3})', 'dkey "12100231231@163.com" 23') >>> data.groupdict() {'mail': '12100231231@163.com', 'name': 'dkey', 'age': '232'}
4. split
re.split(pattern, string[, maxsplit=0, flags=0])
此功能很常用,可以将正则表达式匹配的部分进行分割,并返回一个列表。我们在python中,使用str的方法split也可以做字符串分割,但是使用正则会方便很多。
第一个参数:匹配规则
第二个参数:字符串
第三个参数:最大分割次数,默认为0,表示每个匹配项都分割
对于已编译的正则表达式对象来说(re.RegexObject),有方法:split(string[, maxsplit=0])
例如,利用上面章节中介绍的语法:
# 以:或空格分割; >>> program = "ywnds:C C++ Java Python" >>> re.split(r':| ', program) ['ywnds', 'C', 'C++', 'Java', 'Python'] # 以:或空格或,分割; >>> program = "ywnds:C C++ Java Python,Go" >>> re.split(r':| |,', program) ['ywnds', 'C', 'C++', 'Java', 'Python', 'Go']
对于一个找不到匹配的字符串而言,split不会对其作出分割,如:
>>> re.split(r'a', 'hello world') ['hello world']
5. findall
re.findall(pattern, string[, flags])
在字符串中查找到正则表达式所匹配的所有字符,并组成一个列表返回。跟search方法最大的区别就在于search只会查找到第一个匹配值后就返回,而findall是查找所有。
第一个参数:匹配规则
第二个参数:目标字符串
但三个参数:后面还可以跟一个规则选择项
对于已编译的正则表达式对象来说(re.RegexObject),有方法:findall(string[, pos[, endpos]])
示例如下:
>>> re.findall(r"\d+", 'python=90, java=80, go=70') ['90', '80', '70'] >>> re.search(r"\d+", 'python=90, java=80, go=70') <_sre.SRE_Match object; span=(7, 9), match='90'>
6. finditer
re.finditer(pattern, string[, flags])
和findall方法类似,在字符串中找到正则表达式所匹配的所有字符,并组成一个迭代器返回。
对于已编译的正则表达式对象来说(re.RegexObject),有方法:finditer(string[, pos[, endpos]])
7. sub
re.sub(pattern, repl, string[, count, flags])
将字符串中匹配到正则表达式的部分用另一个字符串repl进行替换。如果没有找到匹配pattern的字符,则返回未被修改的string。repl既可以是字符串也可以是一个函数。
第一个参数:匹配规则
第二个参数:替换后的字符串
第三个参数:字符串
第四个参数:替换个数,默认为0,表示每个匹配项都替换
对于已编译的正则表达式对象来说(re.RegexObject),有方法:sub(repl, string[, count=0])
此语法的示例有:
>>> p = re.compile(r'(one|two|three)') >>> p.sub( 'num', 'one word two words three words') 'num word num words num words'
同样可以用以下方法,并指定count为1(只替换第一个):
>>> p.sub( 'num', ' one word two words three words', count=1) ' num word two words three words'
上面说了,repl也可以是一个函数,下面我们测试一下看看:
def add(v): var = v.group() num = int(var) + 1 return str(num)
上面定义了一个函数,默认会接收一个实例化对象,必须返回一个str对象。
>>> incr = "score = 90" >>> incr = re.sub(r'[\d]+', add, incr) >>> incr 'score = 91' >>> incr = re.sub(r'[\d]+', add, incr) >>> incr 'score = 92'
8. subn
subn(pattern, repl, string[, count, flags])
该函数的功能和sub()相同,但它还返回新的字符串以及替换的次数。
对于已编译的正则表达式对象来说(re.RegexObject),有方法:subn(repl, string[, count=0])