Python——正則表達式(2)
本文译自官方文档:Regular Expression HOWTO
參考文章:Python——正則表達式(1)
全文下载 :Python正則表達式基础
======================================================================================
3.使用正則表達式
如今。我们已经学习了一些简单的正則表達式,但我们应该怎么在Python中使用它们呢?re模块提供了一个连接正則表達式引擎的接口,同意你将RE编译成对象并利用它们进行匹配。
------------------------------------------------------------------------------------------------------------------------------------------------------
3.1.编译正則表達式正則表達式被编译成模式对象,该对象拥有非常多方法来进行各种各样的操作,比方依照模式匹配查找或者运行字符串的替换。
>>> import re >>> p = re.compile('ab*') >>> p re.compile('ab*')re.compile()方法也能够接受一个可选flags參数,用于指定各种特殊功能和语法变更,我们将会在之后一一学习。如今我们看一个简单的样例:
>>> p = re.compile('ab*',re.IGNORECASE)正則表達式作为一个字符串參数传递给re.compile()方法。因为正則表達式并非Python的核心部分,也没有特殊的语法去表示它们。所以仅仅能被作为字符串处理(有很多应用并不须要RE,所以没有必要把RE纳入Python的核心部分)。相反,正則表達式re模块仅作为C的扩展模块嵌入的Python中,就像socket或者zlib模块。
把正則表達式作为字符串也让Python更加简洁,可是也有一个缺点,下边我们就来谈一谈。
------------------------------------------------------------------------------------------------------------------------------------------------------
3.2.麻烦的反斜杠
就像上文描写叙述的。正則表達式使用反斜杠 \ 来使一些字符拥有特殊的意义(比方\s)或者去掉特殊字符的特殊意义(比方\*就是表示星号而没有特殊的意义)。这会与Python字符串中实现同样功能的字符发生冲突。
假如我们要写一个RE来匹配LaTeX文件里的一个字符串“\section”,首先你要先在程序代码中写出将要匹配的字符串。
接着。你须要在反斜杠以及其它元字符前面加上反斜杠以去掉它们的特殊含义。所以得到结果“\\section”,这个字符串将传递给re.compile()函数。然而,要知道Python字符串中的反斜杠也有特殊意义,所以要再次在两个反斜杠前面加上反斜杠。得到字符串“\\\\section”。
匹配字符串 |
匹配步骤 |
\section | 将要匹配的字符串 |
\\section | 正則表達式中用‘\\’表示‘\’ |
“\\\\section” | Python字符串中也用‘\\’表示‘\’ |
这就造成了我们须要反复非常多次反斜杠,也让最后的正則表達式字符串难于理解。
解决方法是使用Python中的原始字符串。所谓原始字符串。即在字符串最前面加上字母r。这样字符串中的反斜杠都会被去掉特殊语义,看做普通字符。比方,字符串 r”\n” 是包括‘\’和‘n’两个字符的字符串。而字符串 “\n” 是仅仅有一个换行符的字符串。正則表達式通常使用Python中的原始字符串来表示。
正則表達式字符串 |
原始字符串 |
“ab*” | r”ab*” |
“\\\\section” | r”\\section” |
“\\w+\\s+\\1” | r”\w+\s+\1” |
3.3.运行匹配
将正則表達式编译之后会得到一个模式对象,那么你会用它做什么呢?模式对象包括很多方法和属性,我们在这里仅仅介绍最经常使用的几个。你能够通过查看re模块的文档来查看完整的列表。
方法/属性 |
功能 |
match() | 推断一个正則表達式是否从開始处匹配一个字符串 |
search() | 扫描一个字符串,找到正則表達式匹配的第一个位置 |
findall() | 扫描一个字符串,找到匹配正則表達式的全部子字符串,并将它们以列表的形式返回 |
finditer() | 扫描一个字符串,找到匹配正則表達式的全部子字符串,并将它们以迭代器的形式返回 |
假设匹配成功,则会返回一个匹配对象,包括匹配的信息:起始位置和匹配的子字符串等等。
你能够在交互模式下使用re模块来学习这些内容。假设你能够使用tkinter。你能够看一下Tools/demo/redemo.py这个程序,这是随着Python公布的一个演示样例程序。它能够让你输入正則表達式和字符串,并输出两者是否匹配。当你測试一个复杂的正則表達式的时候。redemo.py是非常实用的。Phil Schwartz’s Kodos也是一个开发和測试正則表達式的一个非常实用的交互式工具。
我们使用标准Python解释器来解释这些样例。
首先,打开Python解释器,导入re模块。然后编译一个RE:
>>> import re >>> p = re.compile('[a-z]+') >>> p re.compile('[a-z]+')如今你能够利用正則表達式 [a-z]+ 来匹配各种字符串。可是一个空的字符串并不能被匹配,由于加号 + 表示反复1次以上,在这样的情况下,match()方法将会返回None。
另外,这个结果在解释器中不会输出。只是你能够明白地调用print()方法来输出这个结果。
>>> p.match('') >>> print(p.match('')) None接着,让我们尝试一个它能够匹配的字符串。比方字符串“tempo”。
这样的情况下。match()方法将会返回一个匹配对象(match object),为了之后使用这个对象,你应该把这个结果保存在一个变量中。
>>> m = p.match('tempo') >>> m <_sre.SRE_Match object; span=(0, 5), match='tempo'>
如今你能够利用匹配对象查询匹配字符串的信息。
匹配对象实例也有一些方法和属性。这里列出最重要的几个:
方法/属性 | 功能 |
group() | 返回匹配的字符串 |
start() | 返回字符串匹配的開始位置 |
end() | 返回字符串匹配的结束位置 |
span() | 返回一个元祖表示匹配位置。(開始,结束) |
尝试下面这些样例。你能够非常快理解这些方法:
>>> m.group() 'tempo' >>> m.start(),m.end() (0, 5) >>> m.span() (0, 5)group()方法返回由RE.start()和RE.end()位置确定的子字符串。span()方法用一个元祖返回子字符串的開始位置和结束位置。但要注意的是。match()方法是推断正則表達式是否从開始处匹配字符串,所以start()方法总是返回0。
然而,search()方法就不一样了,它扫描整个字符串。匹配的子字符串的開始位置不一定是0。
>>> print(p.match(':::message')) None >>> m = p.search(':::message') >>> print(m) <_sre.SRE_Match object; span=(3, 10), match='message'> >>> m.group() 'message' >>> m.span() (3, 10)在实际的程序中,最经常使用的写法是将匹配对象存储在一个变量中,然后检查它是否为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 = p.finditer('12 drummers drumming, 11 pipers piping, 10 lords a-leaping') >>> iterator <callable_iterator object at 0x036A2110> >>> for match in iterator: print(match.span()) (0, 2) (22, 24) (40, 42)------------------------------------------------------------------------------------------------------------------------------------------------------
3.4.模块级别的函数
你并不须要去创建一个匹配对象来调用它的方法,re模块中也提供一些全局函数比方match()、search()、findadd()、sub()等等。这些函数的第一个參数是正則表達式字符串,其它參数跟模式对象同名的方法採用一样的參数,返回值也一样。都是返回None或者匹配对象。
>>> print(re.match(r'From\s+','Fromage amk')) None >>> print(re.match(r'From\S+','Fromage amk')) <_sre.SRE_Match object; span=(0, 7), match='Fromage'> >>> print(re.match(r'From\S+','Fromage amk').group()) Fromage >>> re.match(r'From\s+','From amk Thu May 14 19:12:10 1998') <_sre.SRE_Match object; span=(0, 5), match='From '>事实上,这些函数仅仅是简单地为你创建了一个模式对象,而且能够调用其相关函数。另外。它将编译好的模式对象存放在缓存中,所以假设之后你使用了同样的正則表達式,就不用了再次创建模式了。能够实现高速调用。
那么你应该使用这些模块级别的函数呢?还是应该先编译得到自己模式对象再调用它的方法呢?假设你要在一个循环中使用正則表達式。提前编译它能够节省函数的调用。
但在循环外部,因为内部缓冲机制。两者的效率不差上下。
------------------------------------------------------------------------------------------------------------------------------------------------------
3.5.编译标志
编译标志能够让你在一些方面改变正則表達式的工作方法。编译标志在re模块中有两个可用名称:全称和简写。比方IGNORECASE的简写是字母I(假设熟悉Perl语言的模式编写,你会知道Perl语言的简写和这个一样,比方re.VERBOSE和简写是re.X)。
多个编译标志能够通过逻辑或连接起来。比方re.I | re.M 设置了I和M两个标志。
下表列出了一些可用的编译标志:
编译标志 |
含义 |
ASCII。A | 使得转义符号如\w,\b,\s和\d仅仅匹配ASCII字符 |
DOTALL,S | 使得点号 . 能够匹配不论什么符号,包含换行符 |
IGNORECASE。I | 匹配不区分大写和小写 |
LOCALE,L | 支持当前的语言(区域)设置 |
MULTILINE,M | 多行匹配,会影响^和$ |
VERBOSE。X(for ‘extended’) | 启用具体的正則表達式 |
I
IGNORECASE
匹配不区分大写和小写;使得字符类和文本字符串在匹配字符的时候不区分大写和小写。比如,[A-Z]也会匹配小写字母。Spam会匹配Spam、spam和spAM。假设你不设置LOCALE标志,则不会考虑语言(区域)设置方法的问题。
L
LOCALE
使得\w、\W、\b和\B取决于当前的语言环境,而不是Unicode数据库。
区域设置是C语言库的一个功能,主要为了在编敲代码的时候考虑语言的差异性。比方,假设你正在处理一个法文文本。你想要写 \w+ 来匹配单词。可是 \w 仅仅匹配出如今字符类[a-zA-Z]中的单词,它不会匹配 'é' 或者 'ç’ ,假设你的系统被设置为法语语言环境,那么C语言函数将会觉得 'é' 也是一个字母。当编译正則表達式的时候设置了LOCALE标志。\w 就能够识别法文了。可是它的速度相对要慢一点。
M
MULTILINE
(^和$我们还没有提到。它们将在后面解说)
通常的,^仅仅匹配字符串的开头,而$仅仅匹配字符串的结尾,当这个标志设置的时候,元字符^将会匹配字符串中每一行的的行首,而类似的,元字符$将会匹配每一行的行尾。
S
DOTALL
使得点号‘.’匹配全部的字符。包含换行符。
假设不设置这个标志,点号‘.’将匹配除了换行符之外的全部字符。
A
ASCII
使得\w、\w、\b、\B和\S值匹配ASCII字符,而不是Unicode字符。这个标志仅对Unicode模式有意义,并忽略字节模式。
X
VERBOSE
这个标志能够让你组织正則表達式更具灵活性。从而写的正則表達式更具有可读性。假设设置了这个标志,正則表達式中的空格将会被忽略,当然这里不包含字符类中的空格。也不包含被反斜杠转义的空格,这会让你更清晰地去组织正則表達式。
另外。这个标志也同意在正則表達式式中使用凝视,井号 # 以及之后的字符将会被正則表達式忽略,除非井号 # 在字符类中或者经过了反斜杠转义。
以下看一个使用re.VERBOSE的样例:
>>> charref = re.compile(r''' &[#] #開始数字引用 ( 0[0-7]+ #八进制格式 |[0-9]+ #十进制格式 |x[0-9a-fA-F]+ #十六进制格式 ) ; #结尾分号 ''',re.VERBOSE)假设不用VERBOSE设置。这个正則表達式将会是以下这个格式:
>>> charref = re.compile('&[#](0[0-7]+' '|[0-9]+' '|x[0-9a-fA-F]+);')在上述的样例中。我们使用了Python自己主动串联字符串的功能,从而将正則表達式分成了几个更小的部分。可是它依然没有使用re.VERBOSE版本号的RE好理解。