gavanwanggw

导航

Python正則表達式:怎样使用正則表達式

正則表達式(简称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。或者a////b。


你能忽略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*$")


posted on 2017-05-03 17:35  gavanwanggw  阅读(278)  评论(0编辑  收藏  举报