Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。Python 1.5之前版本则是通过 regex 模块提供 Emecs 风格的模式。Emacs 风格模式可读性稍差些,而且功能也不强,因此编写新代码时尽量不要再使用 regex 模块,当然偶尔你还是可能在老代码里发现其踪影。
就其本质而言,正则表达式(或 RE)是一种小型的、高度专业化的编程语言,(在Python中)它内嵌在Python中,并通过 re 模块实现。使用这个小型语言,你可以为想要匹配的相应字符串集指定规则;该字符串集可能包含英文语句、e-mail地址、TeX命令或任何你想搞定的东西。然後你可以问诸如“这个字符串匹配该模式吗?”或“在这个字符串中是否有部分匹配该模式呢?”。你也可以使用 RE 以各种方式来修改或分割字符串。
正则表达式模式被编译成一系列的字节码,然後由用 C 编写的匹配引擎执行。在高级用法中,也许还要仔细留意引擎是如何执行给定 RE ,如何以特定方式编写 RE 以令生产的字节码运行速度更快。本文并不涉及优化,因为那要求你已充分掌握了匹配引擎的内部机制。
正则表达式语言相对小型和受限(功能有限),因此并非所有字符串处理都能用正则表达式完成。当然也有些任务可以用正则表达式完成,不过最终表达式会变得异常复杂。碰到这些情形时,编写 Python 代码进行处理可能反而更好;尽管 Python 代码比一个精巧的正则表达式要慢些,但它更易理解。
我们将从最简单的正则表达式学习开始。由于正则表达式常用于字符串操作,那我们就从最常见的任务:字符匹配 下手。
有关正则表达式底层的计算机科学上的详细解释(确定性和非确定性有限自动机),你可以查阅编写编译器相关的任何教科书。
大多数字母和字符一般都会和自身匹配。例如,正则表达式 test 会和字符串“test”完全匹配。(你也可以使用大小写不敏感模式,它还能让这个 RE 匹配“Test”或“TEST”;稍後会有更多解释。)
这个规则当然会有例外;有些字符比较特殊,它们和自身并不匹配,而是会表明应和一些特殊的东西匹配,或者它们会影响到 RE 其它部分的重复次数。本文很大篇幅专门讨论了各种元字符及其作用。
这里有一个元字符的完整列表;其含义会在本指南馀下部分进行讨论。
. ^ $ * + ? { [ ] \ | ( )
我们首先考察的元字符是"[" 和 "]"。它们常用来指定一个字符类别,所谓字符类别就是你想匹配的一个字符集。字符可以单个列出,也可以用“-”号分隔的两个给定字符来表示一个字符区间。例如,[abc] 将匹配"a", "b", 或 "c"中的任意一个字符;也可以用区间[a-c]来表示同一字符集,和前者效果一致。如果你只想匹配小写字母,那幺 RE 应写成 [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,.]字符类将匹配任何空白字符或","或"."。
本节最後一个元字符是 . 。它匹配除了换行字符外的任何字符,在 alternate 模式(re.DOTALL)下它甚至可以匹配换行。"." 通常被用于你想匹配“任何字符”的地方。
正则表达式第一件能做的事是能够匹配不定长的字符集,而这是其它能作用在字符串上的方法所不能做到的。 不过,如果那是正则表达式唯一的附加功能的话,那么它们也就不那么优秀了。它们的另一个功能就是你可以指定正则表达式的一部分的重复次数。
我们讨论的第一个重复功能的元字符是 *。* 并不匹配字母字符 "*";相反,它指定前一个字符可以被匹配零次或更多次,而不是只有一次。
举个例子,ca*t 将匹配 "ct" (0 个 "a" 字符), "cat" (1 个 "a"), "caaat" (3 个 "a" 字符)等等。RE 引擎有各种来自 C 的整数类型大小的内部限制,以防止它匹配超过2亿个 "a" 字符;你也许没有足够的内存去建造那么大的字符串,所以将不会累计到那个限制。
象 * 这样地重复是“贪婪的”;当重复一个 RE 时,匹配引擎会试着重复尽可能多的次数。如果模式的後面部分没有被匹配,匹配引擎将退回并再次尝试更小的重复。
一步步的示例可以使它更加清晰。让我们考虑表达式 a[bcd]*b。它匹配字母 "a",零个或更多个来自类 [bcd]中的字母,最後以 "b" 结尾。现在想一想该 RE 对字符串 "abcbd" 的匹配。
Step | Matched | Explanation |
1 | a | a 匹配模式 |
2 | abcbd | 引擎匹配 [bcd]*,并尽其所能匹配到字符串的结尾 |
3 | Failure | 引擎尝试匹配 b,但当前位置已经是字符的最後了,所以失败 |
4 | abcb | 退回,[bcd]*尝试少匹配一个字符。 |
5 | Failure | 再次尝次b,但在当前最後一位字符是"d"。 |
6 | abc | 再次退回,[bcd]*只匹配 "bc"。 |
7 | abcb | 再次尝试 b ,这次当前位上的字符正好是 "b" |
RE 的结尾部分现在可以到达了,它匹配 "abcb"。这证明了匹配引擎一开始会尽其所能进行匹配,如果没有匹配然後就逐步退回并反复尝试 RE 剩下来的部分。直到它退回尝试匹配 [bcd] 到零次为止,如果随後还是失败,那么引擎就会认为该字符串根本无法匹配 RE 。
另一个重复元字符是 +,表示匹配一或更多次。请注意 * 和 + 之间的不同;*匹配零或更多次,所以根本就可以不出现,而 + 则要求至少出现一次。用同一个例子,ca+t 就可以匹配 "cat" (1 个 "a"), "caaat" (3 个 "a"), 但不能匹配 "ct"。
还有更多的限定符。问号 ? 匹配一次或零次;你可以认为它用于标识某事物是可选的。例如: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 的结果将是上边界为无穷大 -- 实际上是先前我们提到的 2 兆,但这也许同无穷大一样。
细心的读者也许注意到其他三个限定符都可以用这样方式来表示。 {0,} 等同于 *,{1,} 等同于 +,而{0,1}则与 ? 相同。如果可以的话,最好使用 *,+,或?。很简单因为它们更短也再容易懂。
现在我们已经看了一些简单的正则表达式,那么我们实际在 Python 中是如何使用它们的呢? re 模块提供了一个正则表达式引擎的接口,可以让你将 REs 编译成对象并用它们来进行匹配。
正则表达式被编译成 `RegexObject` 实例,可以为不同的操作提供方法,如模式匹配搜索或字符串替换。
#!python
>>> import re
>>> p = re.compile('ab*')
>>> print p
<re.RegexObject instance at 80b4150>
re.compile() 也接受可选的标志参数,常用来实现不同的特殊功能和语法变更。我们稍後将查看所有可用的设置,但现在只举一个例子:
#!python
>>> p = re.compile('ab*', re.IGNORECASE)
RE 被做為一個字串傳送給 re.compile()。 REs 被處理成字串是因為正規表示式不是 Python 語言的核心部分,也沒有為它建立特定的語法。 (應用程式根本不需要 REs,因此沒必要包含它們去使語言說明變得臃腫不堪。)而 re 模組則只是以一個 C 擴展模組的形式來被 Python 包含,就像 socket 或 zlib 模組一樣。
將 REs 作為字串以確保 Python 語言的簡潔,但這樣帶來的一個麻煩就是像下節標題所講的。
在早期規定中,正規表示式用反斜線字元(""") 來表示特殊格式或允許使用特殊字元而不呼叫它的特殊用法。可能是在一個LATEX 檔案查找。為了要在程式碼中判斷,首先要寫出想要匹配的字串。接下來你需要在所有反斜線和元字元前加上反斜線來取消其特殊意義。
#\section | |
\\section | |
"\\\\section" | |
解決的方法就是為正規表示式使用Python 的raw 字串表示;在字串前加個"r" 反斜線就不會被任何特殊方式處理,所以r"\ n" 是包含"\" 和"n" 的兩個字符,而"\n" 則是一個字符,表示一個換行。正規表示式通常在 Python 程式碼中都是用這種 raw 字串表示。
"ab*" | |
"\\\\section" | |
"\\ w+\\s+\\1" | |
match() | |||||||||||||||||||||||||||||||
search() | |||||||||||||||||||||||||||||||
findall() | |||||||||||||||||||||||||||||||
finditer() | |||||||||||||||||||||||||||||||
#方法/屬性 | 作用 | ||||||||||||||||||||
#group() | 傳回被RE 匹配的字串 | ||||||||||||||||||||
##end() | |||||||||||||||||||||
span() | |||||||||||||||||||||
#!python #>>> m.start(), m.end() ## !python #!python 模块级函数你不一定要产生一个 `RegexObject` 对象然後再调用它的方法;re 模块也提供了顶级函数调用如 match()、search()、sub() 等等。这些函数使用 RE 字符串作为第一个参数,而後面的参数则与相应 `RegexObject` 的方法参数相同,返回则要么是 None 要么就是一个 `MatchObject` 的实例。 #!python Under the hood, 这些函数简单地产生一个 RegexOject 并在其上调用相应的方法。它们也在缓存里保存编译後的对象,因此在将来调用用到相同 RE 时就会更快。
#!python 我通常更喜欢使用编译对象,甚至它只用一次,but few people will be as much of a purist about this as I am。 编译标志编译标志让你可以修改正则表达式的一些运行方式。在 re 模块中标志可以使用两个名字,一个是全名如 IGNORECASE,一个是缩写,一字母形式如 I。(如果你熟悉 Perl 的模式修改,一字母形式使用同样的字母;例如 re.VERBOSE的缩写形式是 re.X。)多个标志可以通过按位 OR-ing 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:
I 使匹配对大小写不敏感;字符类和字符串匹配字母时忽略大小写。举个例子,[A-Z]也可以匹配小写字母,Spam 可以匹配 "Spam", "spam", 或 "spAM"。这个小写字母并不考虑当前位置。 L 影响 "w, "W, "b, 和 "B,这取决于当前的本地化设置。 locales 是 C 语言库中的一项功能,是用来为需要考虑不同语言的编程提供帮助的。举个例子,如果你正在处理法文文本,你想用 "w+ 来匹配文字,但 "w 只匹配字符类 [A-Za-z];它并不能匹配 "é" 或 "ç"。如果你的系统配置适当且本地化设置为法语,那么内部的 C 函数将告诉程序 "é" 也应该被认为是一个字母。当在编译正则表达式时使用 LOCALE 标志会得到用这些 C 函数来处理 "w 後的编译对象;这会更慢,但也会象你希望的那样可以用 "w+ 来匹配法文文本。 M
S 使 "." 特殊字符完全匹配任何字符,包括换行;没有这个标志, "." 匹配除了换行外的任何字符。 X
#!python 在上面的例子里,Python 的字符串自动连接可以用来将 RE 分成更小的部分,但它比用 re.VERBOSE 标志时更难懂。 更多模式功能到目前为止,我们只展示了正则表达式的一部分功能。在本节,我们将展示一些新的元字符和如何使用组来检索被匹配的文本部分。 更多的元字符還有一些我們還沒展示的元字符,其中的大部分將在本節展示。
|
^
--> #!python#!python 匹配一个 "$",使用 "$ 或将其包含在字符类中,如[$]。 "A
"Z Matches only at the end of the string. "b 单词边界。这是个零宽界定符(zero-width assertions)只用以匹配单词的词首和词尾。单词被定义为一个字母数字序列,因此词尾就是用空白符或非字母数字符来标示的。
## !python ## !python
##!python ################>>>###### p ########## ##### re.compile(######'######(ab)*######'######)########## ##>>>###### ######print####### p.match(######'######ababababab###### #'######).span()######(0, #######10######)######组用 "(" 和 ")" 来指定,并且得到它们匹配文本的开始和结尾索引;这就可以通过一个参数用 group()、start()、end() 和 span() 来进行检索。组是从 0 开始计数的。组 0 总是存在;它就是整个 RE,所以 `MatchObject` 的方法都把组 0 作为它们缺省的参数。稍後我们将看到怎样表达不能得到它们所匹配文本的 span。 #!python 小组是从左向右计数的,从1开始。组可以被嵌套。计数的数值可以能过从左到右计算打开的括号数来确定。 #!python group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。 ## !python #模式中的逆向引用允許你指定先前捕獲群組的內容,該群組也必須在字串目前位置被找到。舉個例子,如果群組1 的內容能夠在當前位置找到的話,"1 就成功否則失敗。記住Python 字串也是用反斜線加資料來允許字串中包含任意字元的,所以當在RE 中使用逆向引用時確保使用raw 字串。 by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ #!python 除了捕获匹配组的内容之外,无捕获组与捕获组表现完全一样;你可以在其中放置任何字符,可以用重复元字符如 "*" 来重复它,可以在其他组(无捕获组与捕获组)中嵌套它。(?:...) 对于修改已有组尤其有用,因为你可以不用改变所有其他组号的情况下添加一个新组。捕获组和无捕获组在搜索效率方面也没什么不同,没有哪一个比另一个更快。
#!python #!python
#!python 前向界定符另一个零宽界定符(zero-width assertion)是前向界定符。前向界定符包括前向肯定界定符和後向肯定界定符,所下所示: (?=...) 前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩馀部分还要尝试界定符的右边。 (?!...) 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功
.*[.].*$ 注意 "." 需要特殊对待,因为它是一个元字符;我把它放在一个字符类中。另外注意後面的 $; 添加这个是为了确保字符串所有的剩馀部分必须被包含在扩展名中。这个正则表达式匹配 "foo.bar"、"autoexec.bat"、 "sendmail.cf" 和 "printers.conf"。 现在,考虑把问题变得复杂点;如果你想匹配的扩展名不是 "bat" 的文件名?一些不正确的尝试: .# *[.][^b].#*$ 上面的第一次移除"bat" 的嘗試是要求擴展名的第一個字元不是"b"。這是錯誤的,因為該模式也不能符合 "foo.bar"。 . *[.]([^b]..##|.[ ^a].|..[^t])$ #當你試著修補第一個解決方法而要求匹配下列情況之一時表達式更亂了:擴展名的第一個字元不是"b"; 第二個字元不是"a";或第三個字元不是"t"。這樣可以接受 "foo.bar" 而拒絕 "autoexec.bat",但這要求只能是三個字元的副檔名而不接受兩個字元的副檔名如 "sendmail.cf"。我們將在努力修補它時再次把該模式變得複雜。 Code highlighting produced by Actipro CodeHighlighter (freeware) -->.# *[.](?!bat$|exe$).##*##$#修改字串 到目前為止,我們簡單地搜尋了一個靜態字串。正規表示式通常也用不同的方式,透過下面的 `RegexObject` 方法,來修改字串。
|
以上是比較詳細Python正規表示式操作指南的詳細內容。更多資訊請關注PHP中文網其他相關文章!