Chinaunix首页 | 论坛 | 博客
  • 博客访问: 27982
  • 博文数量: 11
  • 博客积分: 1820
  • 博客等级: 上尉
  • 技术积分: 120
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-04 19:45
文章分类
文章存档

2010年(10)

2009年(1)

最近访客

分类: Python/Ruby

2009-01-08 23:52:43

1、序言


re 模块在Python1.5中被加入,并且提供了Perl类型的正则表达式模式。较早的Python版本用的是regex模块,它提供Emacs类型模式。 Emacs类型模式可读性差并且没有提供很多特性,因此当我们写新的代码的时候没有必要去使用regex模块,尽管你可能会碰到一些使用了它的老代码。


正 则表达式本质上是一个很小的且高度专用的程序语言,它嵌入在Python中且通过re模块可用。在使用这个小的语言的时候,你要为你想要匹配的可能的字符 串指定规则;要匹配的字符串可以是英语句子,或e-mail地址,或TeX命令,或你喜欢的任何东西。你可问问题如“这个字符串匹配这个模式吗?”,呀“ 在这个字符串中有与这个模式匹配的吗?”。你可以使用正则表达式去修改一个字符串或用不同的方法分离它。


正则表达式模式被编译成连续的字 节码,这些字节码然后被用C写成的匹配引擎执行。关于高级的使用,必须要仔细关心这个引擎如何去执行一个给定的正则表达式和你写怎样的正则表达式它所产生 的字节码运行的更快。这篇文档不涉及优化,因为它要求你对匹配引擎的内部组织有一个好的理解。


正则表达式语言是一个相对小而有限,因此不 是所有的字符串处理任务都可以用正则表达式来完成。这其中虽然有一些任务可以用正则表达式来完成,但这个表达式是非常复杂的。这种情况下,更好的方法是写 Python代码来处理,尽管Python代码比精巧的正则表达式执行的慢,但它更易理解。

 2、简单模式
2.1、匹配字符

大部分的字母和字符都将只匹配它们自身。例如正则表达式test将正确地匹配字符串“test”(你也可以使用大写不敏感模式使之匹配"Test"或"TEST")。


大部分的字母和字符都将只匹配它们自身,这个规则也有例外;有些字符是专用的,不与自身匹配。相反,它们表示本身意义之外的东西将被匹配,或者通过重复它们来影响正则表达式的其它部分。这个文档的大部分是专门讨论各种元字符和它们的作用。


下面是元字符的完整列表;它们的意义将在文档的其余部分讨论。

. ^ $ * + ? { [ ] \ | ( )

我们首先关注的元字符是"["和"]"。 它们被用来指定一类字符,是一套你想去匹配的字符。字符可以分别列出,或使用两个字符中间用"-"分离来表示一定范围的字符。例如,[abc]将匹 配"a","b",或"c"中的任一个;这与[a-c]是一样的作用,[a-c]指定了要匹配的字符范围。如果你只想匹配小写字母,你的正则表达式应是 [a-z]。

元字符在类别中是不活动的。例如,[akm$]将匹配字符 "a", "k", "m", 或 "$"中的任一个,"$"通常是一个元字符,但是在字符类别中它脱离它的特殊性。


通过补充设置,你可以匹配不在一个范围内的字符。这可通过在字符类别中包括一个"^"作为首字符;例如,[^5]将匹配除了"5"以外的任何字符。


也许最重要的元字符是反斜杠"
\"。如同Python字面上的字符串,反斜杠后可跟不同的字符去表示不同的特殊序列。它也被用来避免所有的元字符以便你仍然可以按模式匹配它们;例如,如果你需要匹配"["或"\",你可以在它们的前面加上一个反斜杠以消除它们特别的意思:\[或\\。

一些以"\"开始的特殊序列代表预定义一套字符,它们通常是有用的,诸如一套数字,一套字母或一套任何东西(不是空白)。下面的预定义的特殊序列是有效的:


\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)匹配换行符。"."经常用于在你想匹配任何字符时。

2.2、重复


我们要关注的这第一个用于重复的元字符是
*,*不能匹配字面意义上的"*";相反,它指定前面的字符能够被匹配0次或更多次。
例如,ca*t将匹配"ct"(0个"a"字符),"cat"(1个"a"),"caaat"(3个"a"字符)等等。

另一个重复操作的元字符是
+,它匹配一次或多次。+要求其前的字符至少要出现一次。例如,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、使用正则表达式
3.1、编译正则表达式

正则表达式被编译成RegexObject实例,它有关于各种操作的方法,如为匹配模式搜索或执行字符串替换。

>>> import re
>>> p = re.compile('ab*')
>>> print p


re.compile()也接受一个可选的标志参数,用来使各种特别的特性和语法变种起作用。下面是一个简单的例子:

>>> p = re.compile('ab*', re.IGNORECASE)

正则表达式被作为一个字符串传递给re.compile()。正则表达式被作为字符串处理是因为它不是核心Python语言的一部分,并且没有为表示它们而创建专门的语法。相反,re模块仅是一个被Python所包括的C扩展模块,就象socket或zlib模块。

3.2、反斜杠的麻烦


如 前所述,正则表达式使用反斜杠来标识特殊的形式或允许特别的字符被使用而避开它们的特殊意义。但这在Python中是很麻烦的。例如你想写一个正则表达式 去匹配字符串"\section',那么正则表达式应是"\\section"。然而在Python中,这个表达式要作为字面意义上的字符串传递给 re.compile(),则应写成"\\\\section"。这样很难理解,解决的办法是使用r前缀,如r"\\section"来表示字面意义上的 字符串。


3.3、执行匹配


一旦你有了一个代表已编译的正则表达式的对象时,你用它来做什么呢?正则表达式对象实例有一些方法和属性。下面是其中最重要的:


match() 
确定正则表达式是否匹配字符串的开头
search() 
扫描字符串以查找匹配
findall() 
找到所有正则表达式匹配的子字符串,并把它们作为一个列表返回
finditer() 
找到所有正则表达式匹配的子字符串,并把它们以指示器的形式返回

match()和search()在没有发现匹配时返回None。如果匹配成功,一个MatchObject实例被返回,其中包含的匹配信息有:哪开始哪结束,匹配的子字符串等等。


你 可以通过交互式的试验使用re模块来学习这个。如果你有一个有效的Tkinter,你可能想关注Tools/scripts/redemo.py,这是一 个包含在Python发行版中的演示程序。它允许你输入正则表达式和字符串,并且显示正则表达式是否匹配。redemo.py在试图调试一个复杂的正则表 达式时可能是十分有用的。我们的文档将使用标准的Python解释器来学习正则表达式。


首先运行Python解释器,引入re模块并用编译正则表达式:

Python 2.2.2 (#1, Feb 10 2003, 12:57:01)
>>> import re
>>> p = re.compile('[a-z]+')
>>> p
<_sre.SRE_Pattern object at 80c3c28>

现在,现在你可以试着用正则表达式[a-z]+去匹配不同的字符串。空字符串不能匹配,因为+意味着一个或多个重复,这种情况下match()将返回None,它将使解释器什么都不输出。你也可以显示的打印match()的结果来让这个更清楚。
>>> p.match("")
>>> print p.match("")
None

现在,让我们试着匹配一个字符串,如"tempo"。这种情况下,match()将返回一个MatchObject,因此你可以存储这个结果到一个变量中以备后用。
>>> m = p.match( 'tempo')
>>> print m
<_sre.SRE_Match object at 80c4f68>


现在,你可以查询MatchObject以获取关于匹配字符串的信息。MatchObject实例也有一些方法和属性;最重要的几个如下:


group() 
返回通过正则表达式匹配到的字符串
start() 
返回成功匹配开始位置
end()   
返回成功匹配结束位置
span()  
返回包含成功匹配开始和结束位置的元组

试一下这些方法将很快清楚它们的意思:

>>> m.group()
'tempo'
>>> m.start(), m.end()
(0, 5)
>>> m.span()
(0, 5)


因为match方法只检查正则表达式是否匹配字符串的开头,所以start()总是返回0值。但是,RegexObject实例的search方法要扫描整个字符串,这种情况下,成功匹配的开头位置不一定会是0值。

>>> print p.match('::: message')
None
>>> m = p.search('::: message') ; print m

>>> m.group()
'message'
>>> m.span()
(4, 11)


在实际的程序中,最通常的方式是存储MatchObject到一个变量中,然后检查它是否是None。类似如下代码:

p = re.compile( ... )
m = p.match( 'string goes here' )
if m:
    print 'Match found: ', m.group()
else:
    print 'No match'


有两个RegexObject的方法返回关于模式的所有的匹配。findall()返回所匹配的字符串的一个列表:

>>> p = re.compile('\d+')
>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')
['12', '11', '10']


finditer()方法返回一个作为指示器的MatchObject实例的序列。

>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator

>>> for match in iterator:
...     print match.span()
...
(0, 2)
(22, 24)
(29, 31)


3.4、模块级函数


你 是非得要产生一个RegexObject,然后调用它的方法;re模块也提供了顶级的函数来调用match(),search(),sub()等等。这些 函数使用与RegexObject方法相应的参数,使用正则表达式字符串作为它们的第一个参数,并且仍然返回None或MatchObject实例。

>>> print re.match(r'From\s+', 'Fromage amk')
None
>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')


隐藏在下面的是这些函数简单地为你产生一个RegexObject并调用它的适当的方法。它们也把被编译了的对象存储在缓存中,以便于以后调用相同的正则表达式更快。


你 是使用模块级的函数还是自己得到RegexObject并调用它的方法呢?这个选择依赖于正则表达式被使用的频度,和你个人的代码风格。如果正则表达式仅 使用在代码中的一处,那么模块级函数或许更方便。如果一个程序包含了大量的正则表达式,或在不同的位置要重复使用,那么在一处集中所有的定义和提早在代码 的一部分中编译所有的正则表达式是值得的。


3.5、编辑标志


编辑标志让你修改正则表达式的一些工作方式。在re模块中标志两种名字都是有效的,长名如IGNORECASE,短名即一个字母如I。可以通过按位或(|)来指定多重标志。例如,re.I|re.M设置I和M标志。


下面是有效标志及说明:


I
IGNORECASE

说明:执行不区分大小写的匹配

L
LOCALE

说明:根据当前的场所使用\w,\W,\b,\B。
场 所是C库的一个特性,它是为了在考虑到不同的语言差异时帮助写程序。例如,如果你在处理一个法国文本,你可能想去写\w+来匹配单词,但是\w仅匹配字符 类别[A-Za-z];它不将匹配"é" 或 "ç". 如果你的系统已恰当的配置并且法语场所已选择,某个C函数将告诉程序"é"也被考虑为一个字母。 在编译正则表达式时设置LOCALE标志将导致编译的结果对象为\w使用C函数,这是较慢的,但能如你所愿的使用\w+去匹配法文。

M
MTLTILINE

说明:多行匹配
针对"^",表示"^"除了匹配字符串的开始位置,也匹配 ’\n’ 或 ’\r’ 之后的位置;
针对"$",表示"$"除了匹配输入字符串的结束位置,也匹配 ’\n’ 或 ’\r’ 之前的位置

S
DOTALL

说明:使得"."专用字符匹配任何字符,包括换行符;没有这个标志,"."将匹配除了换行符以外的任何字符。

X
VERBOSE

说 明:这个标志让你以自己的格式写正则表达式,这样既灵活以可以增加可读性。当这个标志被指定时,正则表达式中的空白将被忽略,除非空白在字符类别中或前面 仅有一个反斜杠;这让你组织和缩排正则表达式更清楚。它也让你能够在正则表达式中放置注释,注释将被引擎忽略;注释通过一个既不在字符类别内也没有单一前 缀"\"的#号标记。
例如下面的正则表达式使用了re.VERBOSE;看看它是多么易读。
charref = re.compile(r"""
 &[#]             # 数值实体参考
 (
   [0-9]+[^0-9]      # 十进制形式
   | 0[0-7]+[^0-7]   # 八进制形式
   | x[0-9a-fA-F]+[^0-9a-fA-F] # 十六进制形式
 )
""", re.VERBOSE)


不要VERBOSE设置,上面这个正则表达式如下所示:

charref = re.compile("&#([0-9]+[^0-9]"
                     "|0[0-7]+[^0-7]"
                     "|x[0-9a-fA-F]+[^0-9a-fA-F])")

4、更多的模式
4.1、更多的元字符

这儿有一些元字符我们仍然还没有涉及。它们中的大多数将在本部分涉及。


或操作符,如果A和B是正则表达式,A|B将匹配"A"或"B"中的任一个。|有非常低的优先级以便于你能在或多个字母的字符串时用它恰当的工作。Crow|Servo将匹配"Crow"或"Servo"的任一个,而不匹配"Cro","w","S","ervo"。
要匹配字面意义上的"|",使用\|,或把它围在一个字符类别中如[|]。

匹配行的开头。除非MULTILINE标志被设置,否则仅匹配字符串的开头。在MULTILINE模式中,也匹配 '\n'或'\r'之后的位置。
例如,如果你想仅在一行的开头位置匹配单词"From",那么正则表达式应使用^From
>>> print re.search('^From', 'From Here to Eternity')

>>> print re.search('^From', 'Reciting From Memory')
None

如果设置了MULTILINE(值为8)模式,例子如下:
>>> print re.search('^From', 'Reciting \nFrom Memory')


匹配一行的末尾,即字符串的末尾或后跟换行符的位置
例如:
>>> print re.search('}$', '{block}')

>>> print re.search('}$', '{block} ')
None
>>> print re.search('}$', '{block}\n')

要匹配字面意义上的"$",使用\$或[$]。

\A
 仅匹配字符串的开头。当不在MULTILINE模式中时,\A和^是作用相同的。在MULTILINE模式中,它们是不同的;\A仍然仅匹配字符串的开头,但是^也匹配'\n'或'\r'之后的位置。

\Z
 仅匹配字符串的末尾。

\b
 单词边界。它仅匹配一个单词的开头或结尾。单词的定义是字母数字字符的一个序列,因此单词的结尾用空白或非字母数字字符标识。
下面的例子仅当"class"是一个独立的词的时候与之匹配;如果它被包含在另一个词中将不匹配。
>>> p = re.compile(r'\bclass\b')
>>> print p.search('no class at all')

>>> print p.search('the declassified algorithm')
None
>>> print p.search('one subclass is')
None

注意\b在Python中是一个退格字符,所以在使用中我们必须将它转化为字面意义上的\b。

\B
 与\b的作用相反。

4.3、分组


你经常需要获得除了正则表达式是否匹配之外的更多的信息。正则表达式经常被用来切片字符串,这通过写一个分组的正则表达式来匹配不同的你感兴趣的部分。


分组使用 "
("和")"元字符。它们把正则表达式分成几个子组。并且你可以使用重复标记来重复一个分组,诸如 *, +, ?, {m,n}。例如,(ab)*将匹配0个或多个"ab"。
>>> p = re.compile('(ab)*')
>>> print p.match('ababababab').span()
(0, 10)
 

用"("和")"标识的分组也捕获它们所匹配文本的开始和结束位置的索引;这个可以通过group(),start(),end(),span()来得到。分组计数从0开始。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')


4.3、无捕获和命名组


精心制作的正则表达式可能使用许多组,有的用来捕获感兴趣的子字符串,有的分组和结构化正则表达式自身。在复杂的正则表达式中,保持跟踪组号变得很困难。这有两个特性有助于解决这个问题。它们为正则表达式的扩展都使用了一个通用的语法。


Perl5 对标准的正则表达式增加了几个附加的特性,Python的re模块支持它们中的大部分。选择一个新的单按键元字符或一个以 "\" 开始的特殊序列来表示 新的特性,而又不会使 Perl 正则表达式与标准正则表达式产生混乱是有难度的。如果你选定"&"作为一个新的元字符,例如,旧的正则表达式中 它还是作为一个通常的字符,并且没有通过\或[&]将它转义。


Perl开发者选择的解决方案是使用(?...)来作为扩展语 法。"?"直接在一个括号的后面是一个语法错误,因为"?"没有东西去重复,因此这不引入任何的兼容性问题。字符直接跟在"?"后面表明某些扩展正在被使 用,如(?=foo)是一样(一个肯定前向界定符),(?:foo)是另一样(包含子表达式foo的一个无捕获组)。


Python对 Perl的扩展语法增加了一个扩展语法。如果问号的第一个字符是"P",你就可以知道它是针对 Python 的扩展。目前有两个这样的扩展: (?P...) 定义了一个命名组,(?P=name)是对命名组的逆向引用。如果Perl5的未来版本使用一个不同的语法增加 类似的特性,在为了兼容目的保留Python专用语法的时候,re模块也将被改变来支持新的语法。


现在我们已经关注了一般的扩展语法,我 们可以返回到在复杂的正则表达式中使用组来简化工作这一特性上来。因为组是被从左到右编号的,并且一个复杂的正则表达式可能使用许多组,所以保持正确组号 的跟踪变得困难,并且修改这样一个复杂的正则表达式是令人烦恼的。在开始时插入一个新组,你可以改变它之后的每个组号。


首先,某些时候你想用一个组来采集正则表达式的一部分,但是对捕获组的内容没兴趣。你可以通过使用一个无捕获组来实现:(?:...),你可以在括号中放置正则表达式。

>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()


除 了不能获得组所匹配的内容以外,无捕获组的行为和捕获组的行为完全一样;你可以在它之中放置任何东西,使用重复元字符如"*"来重复它,把它嵌入其它的组 中等等。当修改一个存在的组的时候(?:...)显得特别的有用,因为你可以在不用改变所有其他组号的情况下添加一个新组。


其次,更重要和强大的是命名组;与用数字指定组不同的是,它可以用名字来指定。


命 名组的语法是Python专用扩展之一:(?:P...)。显然name就是这个组的名字。除了用一个名字与组相关联以外,命名 组的行为也和捕获组相同。MatchObject 的方法处理捕获组时接受的要么是表示组号的整数,要么是包含组名的字符串。命名组仍然可以被给于组号, 因此你可以使用两种方法中的一种来获取关于一个组的信息:

>>> p = re.compile(r'(?P\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'


命名组是方便的,因为它们让你使用容易记住的名字代替了记住号码。这儿有一个来自imaplib模块的例子:

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')来检索比记得使用group(9)容易得多。


因 为逆向引用的语法,象 (...)\1 这样的表达式所引用的是组号,它与用组名代替组号有本质的差别。这也是一个 Python 扩展: (?P=name) ,它表示名为name 的组的内容应该再次在当前位置被发现。为发现成对单词的正则表达式(\b\w+)\s+\1 也可以被写 成 (?P\b\w+)\s+(?P=word): 

>>> p = re.compile(r'(?P\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'


4.4、前向界定符


另一个零宽度的界定符是前向界定符。前向界定符有肯定和否定形式,都是有效的:


(?=...)

说明:肯定前向界定符。如果...所代表的正则表达式成功匹配当前位置则成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有进展,模式的剩余部分还要尝试界定符的右边。 

(?!...)

说明:否定前向界定符。它与肯定前向界定符相反;如果...所代表的正则表达式没有成功匹配当前位置则成功。

通过演示前向界定符的用处将会让这些更具体。考虑一个简单的模式,这个模式匹配一个文件名并把文件名分成基本名和扩展名,基本名和扩展名以"."相隔。例如,"news.rc","new"是基本名,"rc"是扩展名。


匹配这个的模式十分简单:

.*[.].*$

注 意这个"."需要特别对待,我已经把它放入了一个字符类别中[.]。还要注意这个尾部的$,它被增加来确保扩展名中的所有其余部分必须被包括。这个正则表 达式匹配"foo.bar","artoexec.bat","sendmail.cf","printers.conf"等等。

现在考虑把问题复杂化一点;万一你想匹配后缀不是"bat"的文件名怎么办?一些错误的尝试如下:


.*[.][^b].*$

上面的[^b]将会使得也不匹配"foo.bar",这就导致了一个错误,因为它使得模式不会匹配所有后缀名以b开始的文件名。

.*[.]([^b]..|.[^a].|..[^t])$
 
当 你试着修补第一个解决方法而要求匹配下列情况之一时表达式更乱了:扩展名的第一个字符不是 "b"; 或第二个字符不是 "a";或第三个字符不 是 "t"。这样可以接受 "foo.bar" 而拒绝 "autoexec.bat",但这要求只能是三个字符的扩展名而不接受两个字符的扩展名 如 "sendmail.cf"。我们将在努力修补它时再次把该模式变得复杂。

.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$

在第三次尝试中,第二和第三个字母都变成可选,为的是允许匹配比三个字符更短的扩展名,如 "sendmail.cf"。 

该模式现在变得非常复杂,这使它很难读懂。更糟的是,如果问题变化了,你想扩展名不是 "bat" 和 "exe",该模式甚至会变得更复杂和混乱。


前向否定把所有这些裁剪成如下形式:

.*[.](?!bat$).*$

前向的意思:如果表达式 bat 在这里没有匹配,尝试模式的其余部分;如果 bat$ 匹配,整个模式将失败。后面的 $ 被要求是为了确保象 "sample.batch" 这样扩展名以 "bat" 开头的会被允许。

将另一个文件扩展名排除在外现在也容易;简单地将其做为可选项放在界定符中。下面的这个模式将以 "bat" 或 "exe" 结尾的文件名排除在外:

.*[.](?!bat$|exe$).*$

5、修改字符串
split() 将字符串在 RE 匹配的地方分片并生成一个列表
sub() 
找到 RE 匹配的所有子串,并将其用一个不同的字符串替换
subn() 
与 sub() 相同,但返回新的字符串和替换的数量

5.1、将字符串分片


RegexObject 的 split() 方 法在 RE 匹配的地方将字符串分片,并返回这些部分的列表。它类似于字符串的split()方法,但是它提供更多的定界符;split()只支持通过空 白符和固定字符来分片。正如你期望的,这也有一个模块级函数re.split()。


split( string [, maxsplit = 0])

函数说明:通过正则表达式的匹配来分片字符串。如果捕获括号在 RE 中使用,那么它们的内容也会作为结果列表的一部分返回。如果 maxsplit 非零,那么最多只能分出 maxsplit 个分片。 

你可以通过设置 maxsplit 值来限制分片数。当 maxsplit 非零时,最多只能做 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.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.']


5.2、搜索和替换


另一个通常的任务是找到所有模式匹配的字符串并用不同的字符串来替换它们。sub() 方法提供一个替换值(可以是字符串或一个函数),和一个要被处理的字符串。


sub( replacement, string[, count = 0]) 

函数说明:返回被替换后的string。如果模式没有发现,string将被原样返回。可选参数 count 是模式匹配后替换的最大次数;count 必须是非负整数。缺省值是 0 表示替换所有的匹配。 

下面是一个使用sub()方法的简单例子。它使用单词"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() 方法的作用一样,但返回的是包含新字符串和替换执行次数的两元组。

>>> 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"将转换成\\j。逆向引用,如 "\6",将被正则表达式中相应组所匹配的子串替换。这使你可以在替换后的字符串中插入原始文本的一部分。


下面这个例子匹配被 "{" 和 "}" 括起来的单词 "section",并将 "section" 替换成 "subsection"。 

>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First} section{second}')
'subsection{First} subsection{second}'


这 还有一个语法,是关于被(?P...)语法定义命名组。"\g" 将使用被组名所匹配的子串," \g" 使用相应的组号。虽然"\g<2>" 等同于"\2",但是使用在替换字符串不会造成含义的混淆如" \g<2>0"("\20"将被解释为对组20的引用,而不是组2后跟字面意义上的字符"0"。)。这下面的替换都是相同的。

>>> 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是个函数,该函数将会被模式中每一个不重复的匹配所调用。对于每个调用,为了匹配,函数被传入一个MatchObject参数并用可以使用这个信息来计算期望的替换字符串并返回它。


在下面的例子中,替换函数转换十进制到十六进制:

>>> 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()时,模式被作为第一个参数传递。模式可以是一个字符串或一个RegexObject;如果你需要指定正则表达式的标志,你必须使用一个RegexObject作为第一个参数,或在模式中使用嵌入的修饰。如
sub("(?i)b+", "x", "bbbb BBBB") returns 'x x'
6、常见的问题
6.1、使用字符串方法

某些时候使用re模块是个错误。如果你正在匹配一个固定的字符串或一个单一的字符类别,并且你没有使用任何的re特性如IGNORECASE标志,那么就没有必要使用正则表达式。字符串有几个方法来执行对固定字符串的操作,并用通常更快。


举 一个使用一字符串代替另一固定字符串的例子,例如,你要用"deed"代替"word"。re.sub()好像适合用来处理这个,但是考虑 replace()方法。注意,replace()也将替换单词中的"word",转换"swordfish"为"sdeedfish",不过正则表达式 也是可以做到的。(要避免替换单词的一部分,模式不得不写成 \bword\b,这是为了要求 "word" 两边有一个单词边界。这个工作超过了 replace的能力。)


另一个常见的任务是删除一个字符串中的一个字符或用另一个字符替换它。你也许使用如re.sub('\n', ' ', S)来做这种事,但 translate() 能够实现这两个任务,而且比任何正则表达式操作起来更快。 


总之,在使用re模块之前,请先考虑你的问题能否使用一个更快更简单的字符串方法来解决。


6.2、match()与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.search来代替。 正则表达式编译 器对正则表达式做一些分析以便加速寻找匹配的处理工作。如分析指出匹配的第一个字符必须是什么;例如,一个以Crow开始的模式必须匹配以一个"C"开始 的。分析机可以让引擎快速扫描字符串以找到开始字符,并只在 "C" 被发现后才试着全部匹配。


添加 .* 会使这个优化失败,这就要扫描到字符串尾部,然后回溯以找到 RE 剩余部分的匹配。请使用 re.search() 代替。


6.3、贪婪的与不贪婪的


当重复一个正则表达式时,如用 a*,操作结果是尽可能多地匹配模式。当你试着匹配一对对称的定界符,如 HTML 标志中的尖括号时这个结果经常会困扰你。匹配单个HTML标记的无知的模式不工作,因为 .* 的本质是“贪婪”的。

>>> s = 'Title'
>>> len(s)
32
>>> print re.match('<.*>', s).span()
(0, 32)
>>> print re.match('<.*>', s).group()
Title

匹配从 "" 中的 ">",这并不是你所想要的结果。

在 这种情况下,解决的方案是使用不贪婪的限定符 *?、+?、?? 或 {m,n}?,这将尽可能匹配小的文本。在上面的例子里, ">" 在第一 个 "<" 匹配之后被立即尝试,当它失败时,引擎一次增加一个字符,并在每步重试 ">"。这个处理将得到正确的结果: 

>>> print re.match('<.*?>', s).group()


注意用正则表达式分析 HTML 或 XML 是痛苦的。象这样的任务用 HTML 或 XML 解析器。


6.4、不使用re.VERBOSE


现在你大概注意到正则表达式是非常简洁的符号,但它们不是易读的。中度复杂的正则表达式可以变成反斜杠、圆括号和元字符的长长集合,使它们难以被读懂。


对于这样的正则表达式,当编译正则表达式时指定 re.VERBOSE 标志是有帮助的,因为它允许你编辑正则表达式的格式使之更清楚。


re.VERBOSE 标 志有点作用。正则表达式中不在字符类中的空白符被忽略。这就意味着象 dog | cat 这样的表达式和可读性差的 dog|cat等同, 但 [a b] 将仍就匹配字符 "a"、"b" 或 空格。另外,你也可以把注释放到正则表达式中;注释是从 "#" 一直到换行。当使用三引号字符串 时,可以使正则表达式更优美:

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*$")
阅读(1883) | 评论(0) | 转发(0) |
0

上一篇:ddddddddddddddddddd

下一篇:没有了

给主人留下些什么吧!~~