正则表达式相对小并且存在限制,所以不是所有的字符串处理任务都能用正则表达式解决。也存在有些任务可以用正则表达式做,但表达式非常复杂。在这些情况下,更好的选择是使用Python代码处理,但Python代码相对正则表达式会更慢,但却可能更好理解。
. ^ $ * + ? { } [ ] \ | ( )
首先我们看[和],他们被用于指定一个字符类,表示你希望匹配的一套字符集。字符能被单独列出,或者使用'-'来指示字符的范围,例如:[abc]将匹配字符a、b或c的任意一个;[a-c]也是匹配a、b或c中的任意一个。如果你想匹配仅小写字母,那么RE应该为[a-z]。
>>> 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模块一样。
>>> 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'>
现在你能询问匹配对象关于匹配字符串的信息。匹配对象也有几个方法和属性,最重要的几个是:
>>> 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
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
>>> 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将不需要重新编译。
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更难理解。
>>> 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='}'>
为了匹配字符'$',需要使用\$或者将它分装到字符类中,作为[$]。
>>> 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中的含义表示一致。
>>> 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中必须使用原始字符串表示。
>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('Paris in the the spring').group()
'the the'
这种匹配方式在搜索中很少使用,但在字符串替换时却非常有用。
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
除了你不能获取组匹配的内容,一个非捕获组的行为和捕获组的行为完全一致,你能放任何内容在它里面,可以使用重复元字符(例如*)重复它,或者嵌套其它组(捕获或者非捕获)。当修改一个已经存在的模式时(?:...)是特别有用的,因为你可以增加新的组而不改变已有的组的编号。但需要注意,使用非捕获组和捕获组在匹配上没有任何效率上的不同。
>>> p = re.compile(r'(?P\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
命名组是便利的,因为名称比编号更容易记忆,下面是一个来自imaplib模块的RE的例子:
InternalDate = re.compile(r'INTERNALDATE "'
r'(?P[ 123][0-9])-(?P[A-Z][a-z][a-z])-'
r'(?P[0-9][0-9][0-9][0-9])'
r' (?P[0-9][0-9]):(?P[0-9][0-9]):(?P[0-9][0-9])'
r' (?P[-+])(?P[0-9][0-9])(?P[0-9][0-9])'
r'"')
显然使用名称的方式m.group('zonem')比使用组编号9获取匹配值的方式更加容易使用。
>>> p = re.compile(r'(?P\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'
>>> 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.']
>>> 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中的对应组匹配的子字符串取代。这让你在替换后的结果字符串中能合并原始字符串的部分。
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'
也可以使用(?P>>> p = re.compile('section{ (?P [^}]* ) }', 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}','section{First}')
'subsection{First}'
replacement也可以是一个函数,可以给你更多的控制。如果replacement是一个函数,函数会处理每一个模式匹配的非重叠的子字符串。在每次调用,函数被传递一个匹配对象作为参数,函数可以使用这个信息计算替换字符串并返回它。
>>> 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'。
>>> 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'被发现时才继续向下匹配。
>>> s = 'Title '
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
Title
RE在中匹配'<',然后.*消费字符串其余的所有部分,由于RE最后的>不能匹配,于是正则表达式引擎不得不回溯字符直到它为>找到一个匹配。最后的匹配就是从的'<'到的'>',并不是你想要的。
>>> print(re.match('<.*?>', s).group())
(注意使用正则表达式解析HTML或者XML是痛苦的。因为写一个能处理所有场景的正则表达式是非常复杂的,使用HTML或者XML解析器来完成这样的任务。)
pat = re.compile(r"""
\s* # Skip leading whitespace
(?P[^:]+) # Header name
\s* : # Whitespace, and a colon
(?P.*?) # 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[^:]+)\s*:(?P.*?)\s*$")