新聞中心
正則表達(dá)式HOWTO
作者

專(zhuān)注于為中小企業(yè)提供成都網(wǎng)站制作、做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)隆化免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了上千多家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
A.M. Kuchling
摘要
本文是關(guān)于在 python 中通過(guò) re 模塊使用正則表達(dá)式的入門(mén)教程。它提供了比“標(biāo)準(zhǔn)庫(kù)參考”的相關(guān)章節(jié)更平易的介紹。
概述
正則表達(dá)式(Regular expressions,也叫 REs、 regexs 或 regex patterns),本質(zhì)上是嵌入 Python 內(nèi)部并通過(guò) re 模塊提供的一種微小的、高度專(zhuān)業(yè)化的編程語(yǔ)言。使用這種小語(yǔ)言,你可以為想要匹配的可能字符串編寫(xiě)規(guī)則;這些字符串可能是英文句子、郵箱地址、TeX 命令或任何你喜歡的內(nèi)容。然后,你可以提出諸如“此字符串是否與表達(dá)式匹配?”、“字符串中是否存在表達(dá)式的匹配項(xiàng)?”之類(lèi)的問(wèn)題。你還可以用正則來(lái)修改字符串,或以各種方式將其拆分。
正則表達(dá)式會(huì)被編譯成一系列字節(jié)碼,然后由 C 語(yǔ)言編寫(xiě)的匹配引擎執(zhí)行。對(duì)于高級(jí)用途,可能有必要特別注意引擎將如何執(zhí)行一個(gè)給定的正則,并以某種方式寫(xiě)入正則,以生成運(yùn)行更快的字節(jié)碼。本文不涉及優(yōu)化問(wèn)題,因?yàn)檫@要求你對(duì)正則引擎的匹配過(guò)程有很好的了解。
正則表達(dá)式語(yǔ)言相對(duì)較小且受限,因此并非所有可能的字符串處理任務(wù)都可以使用正則表達(dá)式完成。有些任務(wù)盡管*可以*用正則表達(dá)式來(lái)完成,但表達(dá)式會(huì)變得非常復(fù)雜。這些情況下,最好通過(guò)編寫(xiě) Python 代碼來(lái)進(jìn)行處理。也許 Python 代碼會(huì)比精心設(shè)計(jì)的正則表達(dá)式慢,但它可能更容易理解。
簡(jiǎn)單正則
讓我們從最簡(jiǎn)單的正則表達(dá)式開(kāi)始吧。由于正則表達(dá)式是用來(lái)操作字符串的,我們將從最常見(jiàn)的任務(wù)開(kāi)始:匹配字符。
關(guān)于正則表達(dá)式背后的計(jì)算機(jī)科學(xué)的詳細(xì)解釋?zhuān)ù_定性和非確定性有限自動(dòng)機(jī)),你可以參考幾乎所有關(guān)于編寫(xiě)編譯器的教科書(shū)。
匹配字符
大多數(shù)字母和符號(hào)都會(huì)簡(jiǎn)單地匹配自身。例如,正則表達(dá)式 test 將會(huì)精確地匹配到 test 。(你可以啟用不區(qū)分大小寫(xiě)模式,讓這個(gè)正則也匹配 Test 或 TEST ,稍后會(huì)詳細(xì)介紹。)
但該規(guī)則有例外。有些字符是特殊的 元字符(metacharacters),并不匹配自身。事實(shí)上,它們表示匹配一些非常規(guī)的內(nèi)容,或者通過(guò)重復(fù)它們或改變它們的含義來(lái)影響正則的其他部分。本文的大部分內(nèi)容都致力于討論各種元字符及其作用。
這是元字符的完整列表。它們的含義將在本 HOWTO 的其余部分進(jìn)行討論。
. ^ $ * + ? { } [ ] \ | ( )
首先介紹的元字符是 [ 和 ] 。這兩個(gè)元字符用于指定一個(gè)字符類(lèi),也就是你希望匹配的字符的一個(gè)集合。這些字符可以單獨(dú)地列出,也可以用字符范圍來(lái)表示(給出兩個(gè)字符并用 '-' 分隔)。例如,[abc] 將匹配 a、b、c 之中的任意一個(gè)字符;這與 [a-c] 相同,后者使用一個(gè)范圍來(lái)表達(dá)相同的字符集合。如果只想匹配小寫(xiě)字母,則正則表達(dá)式將是 [a-z] 。
元字符(除了 \)在字符類(lèi)中是不起作用的。 例如,[akm$] 將會(huì)匹配以下任一字符 'a', 'k', 'm' 或 $ 。$ 通常是一個(gè)元字符,但在一個(gè)字符類(lèi)中它的特殊性被消除了。
你可以通過(guò)對(duì)集合 取反 來(lái)匹配字符類(lèi)中未列出的字符。方法是把 '^' 放在字符類(lèi)的最開(kāi)頭。 例如,[^5] 將匹配除 '5' 之外的任何字符。 如果插入符出現(xiàn)在字符類(lèi)的其他位置,則它沒(méi)有特殊含義。 例如:[5^] 將匹配 '5' 或 '^'。
也許最重要的元字符是反斜杠,\ 。 與 Python 字符串字面量一樣,反斜杠后面可以跟各種字符來(lái)表示各種特殊序列。它還用于轉(zhuǎn)義元字符,以便可以在表達(dá)式中匹配元字符本身。例如,如果需要匹配一個(gè) [ 或 \ ,可以在其前面加上一個(gè)反斜杠來(lái)消除它們的特殊含義:\[ 或 \\ 。
一些以 '\' 開(kāi)頭的特殊序列表示預(yù)定義的字符集合,這些字符集通常很有用,例如數(shù)字集合、字母集合或非空白字符集合。
讓我們舉一個(gè)例子:\w 匹配任何字母數(shù)字字符。 如果正則表達(dá)式以 bytes 類(lèi)型表示,\w 相當(dāng)于字符類(lèi) [a-zA-Z0-9_] 。如果正則表達(dá)式是 str 類(lèi)型,\w 將匹配由 unicodedata 模塊提供的 Unicode 數(shù)據(jù)庫(kù)中標(biāo)記為字母的所有字符。 通過(guò)在編譯正則表達(dá)式時(shí)提供 re.ASCII 標(biāo)志,可以在 str 表達(dá)式中使用較為狹窄的 \w 定義。
以下為特殊序列的不完全列表。 有關(guān) Unicode 字符串正則表達(dá)式的序列和擴(kuò)展類(lèi)定義的完整列表,參見(jiàn)標(biāo)準(zhǔn)庫(kù)參考中 正則表達(dá)式語(yǔ)法 的最后一部分 。通常,Unicode 版本的字符類(lèi)會(huì)匹配 Unicode 數(shù)據(jù)庫(kù)的相應(yīng)類(lèi)別中的任何字符。
\d
匹配任何十進(jìn)制數(shù)字,等價(jià)于字符類(lèi) [0-9] 。
\D
匹配任何非數(shù)字字符,等價(jià)于字符類(lèi) [^0-9] 。
\s
匹配任何空白字符,等價(jià)于字符類(lèi) [ \t\n\r\f\v] 。
\S
匹配任何非空白字符,等價(jià)于字符類(lèi) [^ \t\n\r\f\v] 。
\w
匹配任何字母與數(shù)字字符,等價(jià)于字符類(lèi) [a-zA-Z0-9_] 。
\W
匹配任何非字母與數(shù)字字符,等價(jià)于字符類(lèi) [^a-zA-Z0-9_] 。
這些序列可以包含在字符類(lèi)中。 例如,[\s,.] 是一個(gè)匹配任何空白字符、',' 或 '.' 的字符類(lèi)。
本節(jié)的最后一個(gè)元字符是 . 。 它匹配除換行符之外的任何字符,并且有一個(gè)可選模式( re.DOTALL ),在該模式下它甚至可以匹配換行符。 . 通常用于你想匹配“任何字符”的場(chǎng)景。
重復(fù)
能夠匹配各種各樣的字符集合是正則表達(dá)式可以做到的第一件事,而這是字符串方法所不能做到的。但是,如果正則表達(dá)式就只有這么一個(gè)附加功能,它很難說(shuō)的上有多大優(yōu)勢(shì)。另一個(gè)功能是,你可以指定正則的某部分必須重復(fù)一定的次數(shù)。
我們先來(lái)說(shuō)說(shuō)重復(fù)元字符 * 。 * 并不是匹配一個(gè)字面字符 '*' 。實(shí)際上,它指定前一個(gè)字符可以匹配零次或更多次,而不是只匹配一次。
例如,ca*t 將匹配 'ct' ( 0 個(gè) 'a' )、'cat' ( 1 個(gè) 'a' )、 'caaat' ( 3 個(gè) 'a' )等等。
類(lèi)似 * 這樣的重復(fù)是 貪婪的 。當(dāng)重復(fù)正則時(shí),匹配引擎將嘗試重復(fù)盡可能多的次數(shù)。 如果表達(dá)式的后續(xù)部分不匹配,則匹配引擎將回退并以較少的重復(fù)次數(shù)再次嘗試。
通過(guò)一個(gè)逐步示例更容易理解這一點(diǎn)。讓我們分析一下表達(dá)式 a[bcd]*b 。 該表達(dá)式首先匹配一個(gè)字母 'a' ,接著匹配字符類(lèi) [bcd] 中的零個(gè)或更多個(gè)字母,最后以一個(gè) 'b' 結(jié)尾。 現(xiàn)在想象一下用這個(gè)正則來(lái)匹配字符串 'abcbd' 。
|
步驟 |
匹配 |
說(shuō)明 |
|---|---|---|
此時(shí)正則表達(dá)式已經(jīng)到達(dá)了盡頭,并且匹配到了 'abcb' 。 這個(gè)例子演示了匹配引擎一開(kāi)始會(huì)盡其所能地進(jìn)行匹配,如果沒(méi)有找到匹配,它將逐步回退并重試正則的剩余部分,如此往復(fù),直至 [bcd]* 只匹配零次。如果隨后的匹配還是失敗了,那么引擎會(huì)宣告整個(gè)正則表達(dá)式與字符串匹配失敗。
另一個(gè)重復(fù)元字符是 + ,表示匹配一次或更多次。請(qǐng)注意 * 與 + 之間的差別。* 表示匹配 零次 或更多次,也就是說(shuō)它所重復(fù)的內(nèi)容是可以完全不出現(xiàn)的。而 + 則要求至少出現(xiàn)一次。舉一個(gè)類(lèi)似的例子,ca+t 可以匹配 'cat' ( 1 個(gè)``‘a(chǎn)’`` )或 'caaat' ( 3 個(gè) 'a'),但不能匹配 'ct' 。
There are two more repeating operators or quantifiers. The question mark character, ?, matches either once or zero times; you can think of it as marking something as being optional. For example, home-?brew matches either 'homebrew' or 'home-brew'.
The most complicated quantifier is {m,n}, where m and n are decimal integers. This quantifier means there must be at least m repetitions, and at most n. For example, a/{1,3}b will match 'a/b', 'a//b', and 'a///b'. It won’t match 'ab', which has no slashes, or 'a////b', which has four.
m 和 n 不是必填的,缺失的情況下會(huì)設(shè)定為默認(rèn)值。缺失 m 會(huì)解釋為最少重復(fù) 0 次 ,缺失 n 則解釋為最多重復(fù)無(wú)限次。
Readers of a reductionist bent may notice that the three other quantifiers can all be expressed using this notation. {0,} is the same as *, {1,} is equivalent to +, and {0,1} is the same as ?. It’s better to use *, +, or ? when you can, simply because they’re shorter and easier to read.
使用正則表達(dá)式
現(xiàn)在我們已經(jīng)了解了一些簡(jiǎn)單的正則表達(dá)式,那么我們?nèi)绾卧?Python 中實(shí)際使用它們呢? re 模塊提供了正則表達(dá)式引擎的接口,可以讓你將正則編譯為對(duì)象,然后用它們來(lái)進(jìn)行匹配。
編譯正則表達(dá)式
正則表達(dá)式被編譯成模式對(duì)象,模式對(duì)象具有各種操作的方法,例如搜索模式匹配或執(zhí)行字符串替換。:
>>> import re>>> p = re.compile('ab*')>>> pre.compile('ab*')
re.compile() 也接受一個(gè)可選的 flags 參數(shù),用于啟用各種特殊功能和語(yǔ)法變體。 我們稍后將介紹可用的設(shè)置,但現(xiàn)在只需一個(gè)例子
>>> p = re.compile('ab*', re.IGNORECASE)
正則作為字符串傳遞給 re.compile() 。 正則被處理為字符串,因?yàn)檎齽t表達(dá)式不是核心Python語(yǔ)言的一部分,并且沒(méi)有創(chuàng)建用于表達(dá)它們的特殊語(yǔ)法。 (有些應(yīng)用程序根本不需要正則,因此不需要通過(guò)包含它們來(lái)擴(kuò)展語(yǔ)言規(guī)范。)相反,re 模塊只是Python附帶的C擴(kuò)展模塊,就類(lèi)似于 socket 或 zlib 模塊。
將正則放在字符串中可以使 Python 語(yǔ)言更簡(jiǎn)單,但有一個(gè)缺點(diǎn)是下一節(jié)的主題。
反斜杠災(zāi)難
如前所述,正則表達(dá)式使用反斜杠字符 ('\') 來(lái)表示特殊形式或允許使用特殊字符而不調(diào)用它們的特殊含義。 這與 Python 在字符串文字中用于相同目的的相同字符的使用相沖突。
假設(shè)你想要編寫(xiě)一個(gè)與字符串 \section 相匹配的正則,它可以在 LaTeX 文件中找到。 要找出在程序代碼中寫(xiě)入的內(nèi)容,請(qǐng)從要匹配的字符串開(kāi)始。 接下來(lái),您必須通過(guò)在反斜杠前面添加反斜杠和其他元字符,從而產(chǎn)生字符串 \\section。 必須傳遞給 re.compile() 的結(jié)果字符串必須是 \\section。 但是,要將其表示為 Python 字符串文字,必須 再次 轉(zhuǎn)義兩個(gè)反斜杠。
|
字符 |
階段 |
|---|---|
簡(jiǎn)而言之,要匹配文字反斜杠,必須將 '\\\\' 寫(xiě)為正則字符串,因?yàn)檎齽t表達(dá)式必須是 \\,并且每個(gè)反斜杠必須表示為 \\ 在常規(guī)Python字符串字面中。 在反復(fù)使用反斜杠的正則中,這會(huì)導(dǎo)致大量重復(fù)的反斜杠,并使得生成的字符串難以理解。
解決方案是使用 Python 的原始字符串表示法來(lái)表示正則表達(dá)式;反斜杠不以任何特殊的方式處理前綴為 'r' 的字符串字面,因此 r"\n" 是一個(gè)包含 '\' 和 'n' 的雙字符字符串,而 "\n" 是一個(gè)包含換行符的單字符字符串。 正則表達(dá)式通常使用這種原始字符串表示法用 Python 代碼編寫(xiě)。
此外,在正則表達(dá)式中有效但在 Python 字符串文字中無(wú)效的特殊轉(zhuǎn)義序列現(xiàn)在導(dǎo)致 DeprecationWarning 并最終變?yōu)?SyntaxError。 這意味著如果未使用原始字符串表示法或轉(zhuǎn)義反斜杠,序列將無(wú)效。
|
常規(guī)字符串 |
原始字符串 |
|---|---|
應(yīng)用匹配
一旦你有一個(gè)表示編譯正則表達(dá)式的對(duì)象,你用它做什么? 模式對(duì)象有幾種方法和屬性。 這里只介紹最重要的內(nèi)容;請(qǐng)參閱 re 文檔獲取完整列表。
|
方法 / 屬性 |
目的 |
|---|---|
如果沒(méi)有找到匹配, match() 和 search() 返回 None 。如果它們成功, 一個(gè) 匹配對(duì)象 實(shí)例將被返回,包含匹配相關(guān)的信息:起始和終結(jié)位置、匹配的子串以及其它。
你可以通過(guò)交互式實(shí)驗(yàn) re 模塊來(lái)了解這一點(diǎn)。 如果你有 tkinter,你可能還想查看 Tools/demo/redemo.py,這是 Python 發(fā)行附帶的演示程序。 它允許你輸入正則和字符串,并顯示RE是匹配還是失敗。 redemo.py 在嘗試調(diào)試復(fù)雜的正則時(shí)非常有用。
本 HOWTO 使用標(biāo)準(zhǔn) Python 解釋器作為示例。 首先,運(yùn)行 Python 解釋器,導(dǎo)入 re 模塊,然后編譯一個(gè)正則
>>> import re>>> p = re.compile('[a-z]+')>>> pre.compile('[a-z]+')
現(xiàn)在,你可以嘗試匹配正則 [a-z]+ 的各種字符串。 空字符串根本不匹配,因?yàn)?+ 表示“一次或多次重復(fù)”。 match() 在這種情況下應(yīng)返回 None,這將導(dǎo)致解釋器不打印輸出。 你可以顯式打印 match() 的結(jié)果,使其清晰。:
>>> p.match("")>>> print(p.match(""))None
現(xiàn)在,讓我們嘗試一下它應(yīng)該匹配的字符串,例如 tempo。在這個(gè)例子中 match() 將返回一個(gè) 匹配對(duì)象,因此你應(yīng)該將結(jié)果儲(chǔ)存到一個(gè)變量中以供稍后使用。
>>> m = p.match('tempo')>>> m
現(xiàn)在你可以檢查 匹配對(duì)象 以獲取有關(guān)匹配字符串的信息。 匹配對(duì)象實(shí)例也有幾個(gè)方法和屬性;最重要的是:
|
方法 / 屬性 |
目的 |
|---|---|
嘗試這些方法很快就會(huì)清楚它們的含義:
>>> m.group()'tempo'>>> m.start(), m.end()(0, 5)>>> m.span()(0, 5)
group() 返回正則匹配的子字符串。 start() 和 end() 返回匹配的起始和結(jié)束索引。 span() 在單個(gè)元組中返回開(kāi)始和結(jié)束索引。 由于 match() 方法只檢查正則是否在字符串的開(kāi)頭匹配,所以 start() 將始終為零。 但是,模式的 search() 方法會(huì)掃描字符串,因此在這種情況下匹配可能不會(huì)從零開(kāi)始。:
>>> print(p.match('::: message'))None>>> m = p.search('::: message'); print(m)>>> m.group()'message'>>> m.span()(4, 11)
在實(shí)際程序中,最常見(jiàn)的樣式是在變量中存儲(chǔ) 匹配對(duì)象,然后檢查它是否為 None。 這通??雌饋?lái)像:
p = re.compile( ... )m = p.match( 'string goes here' )if m:print('Match found: ', m.group())else:print('No match')
兩種模式方法返回模式的所有匹配項(xiàng)。 findall() 返回匹配字符串的列表:
>>> p = re.compile(r'\d+')>>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping')['12', '11', '10']
在這個(gè)例子中需要 r 前綴,使字面為原始字符串字面,因?yàn)槠胀ǖ摹凹庸ぁ弊址置嬷械霓D(zhuǎn)義序列不能被 Python 識(shí)別為正則表達(dá)式,導(dǎo)致 DeprecationWarning 并最終產(chǎn)生 SyntaxError。 請(qǐng)參閱 反斜杠災(zāi)難。
findall() 必須先創(chuàng)建整個(gè)列表才能返回結(jié)果。 finditer() 方法將一個(gè) 匹配對(duì)象 的序列返回為一個(gè) iterator
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')>>> iterator>>> for match in iterator:... print(match.span())...(0, 2)(22, 24)(29, 31)
模塊級(jí)函數(shù)
你不必創(chuàng)建模式對(duì)象并調(diào)用其方法;re 模塊還提供了頂級(jí)函數(shù) match(),search(),findall(),sub() 等等。 這些函數(shù)采用與相應(yīng)模式方法相同的參數(shù),并將正則字符串作為第一個(gè)參數(shù)添加,并仍然返回 None 或 匹配對(duì)象 實(shí)例。:
>>> print(re.match(r'From\s+', 'Fromage amk'))None>>> re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')
本質(zhì)上,這些函數(shù)只是為你創(chuàng)建一個(gè)模式對(duì)象,并在其上調(diào)用適當(dāng)?shù)姆椒ā?它們還將編譯對(duì)象存儲(chǔ)在緩存中,因此使用相同的未來(lái)調(diào)用將不需要一次又一次地解析該模式。
你是否應(yīng)該使用這些模塊級(jí)函數(shù),還是應(yīng)該自己獲取模式并調(diào)用其方法? 如果你正在循環(huán)中訪(fǎng)問(wèn)正則表達(dá)式,預(yù)編譯它將節(jié)省一些函數(shù)調(diào)用。 在循環(huán)之外,由于有內(nèi)部緩存,沒(méi)有太大區(qū)別。
編譯標(biāo)志
編譯標(biāo)志允許你修改正則表達(dá)式的工作方式。 標(biāo)志在 re 模塊中有兩個(gè)名稱(chēng),長(zhǎng)名稱(chēng)如 IGNORECASE 和一個(gè)簡(jiǎn)短的單字母形式,例如 I。 (如果你熟悉 Perl 的模式修飾符,則單字母形式使用和其相同的字母;例如, re.VERBOSE 的縮寫(xiě)形式為 re.X。)多個(gè)標(biāo)志可以 通過(guò)按位或運(yùn)算來(lái)指定它們;例如,re.I | re.M 設(shè)置 I 和 M 標(biāo)志。
這是一個(gè)可用標(biāo)志表,以及每個(gè)標(biāo)志的更詳細(xì)說(shuō)明。
|
旗標(biāo) |
含意 |
|---|---|
I
IGNORECASE
執(zhí)行不區(qū)分大小寫(xiě)的匹配;字符類(lèi)和字面字符串將通過(guò)忽略大小寫(xiě)來(lái)匹配字母。 例如,[A-Z] 也匹配小寫(xiě)字母。 除非使用 ASCII 標(biāo)志來(lái)禁用非ASCII匹配,否則完全 Unicode 匹配也有效。 當(dāng) Unicode 模式 [a-z] 或 [A-Z] 與 IGNORECASE 標(biāo)志結(jié)合使用時(shí),它們將匹配 52 個(gè) ASCII 字母和 4 個(gè)額外的非 ASCII 字母:’?’ (U+0130,拉丁大寫(xiě)字母 I,帶上面的點(diǎn)),’?’ (U+0131,拉丁文小寫(xiě)字母無(wú)點(diǎn) i),’s’ (U+017F,拉丁文小寫(xiě)字母長(zhǎng) s) 和’K’ (U+212A,開(kāi)爾文符號(hào))。 Spam 將匹配 'Spam','spam','spAM' 或 '?pam' (后者僅在 Unicode 模式下匹配)。 此小寫(xiě)不考慮當(dāng)前區(qū)域設(shè)置;如果你還設(shè)置了 LOCALE 標(biāo)志,則將考慮。
L
LOCALE
使 \w、\W、\b、\B 和大小寫(xiě)敏感匹配依賴(lài)于當(dāng)前區(qū)域而不是 Unicode 數(shù)據(jù)庫(kù)。
區(qū)域設(shè)置是 C 庫(kù)的一個(gè)功能,旨在幫助編寫(xiě)考慮到語(yǔ)言差異的程序。例如,如果你正在處理編碼的法語(yǔ)文本,那么你希望能夠編寫(xiě) \w+ 來(lái)匹配單詞,但 \w 只匹配字符類(lèi) [A-Za-z] 字節(jié)模式;它不會(huì)匹配對(duì)應(yīng)于 é 或 ? 的字節(jié)。如果你的系統(tǒng)配置正確并且選擇了法語(yǔ)區(qū)域設(shè)置,某些C函數(shù)將告訴程序?qū)?yīng)于 é 的字節(jié)也應(yīng)該被視為字母。在編譯正則表達(dá)式時(shí)設(shè)置 LOCALE 標(biāo)志將導(dǎo)致生成的編譯對(duì)象將這些C函數(shù)用于 \w;這比較慢,但也可以使 \w+ 匹配你所期望的法語(yǔ)單詞。在 Python 3 中不鼓勵(lì)使用此標(biāo)志,因?yàn)檎Z(yǔ)言環(huán)境機(jī)制非常不可靠,它一次只處理一個(gè)“文化”,它只適用于 8 位語(yǔ)言環(huán)境。默認(rèn)情況下,Python 3 中已經(jīng)為 Unicode(str)模式啟用了 Unicode 匹配,并且它能夠處理不同的區(qū)域/語(yǔ)言。
M
MULTILINE
(^ 和 $ 還沒(méi)有解釋?zhuān)凰鼈儗⒃谝韵虏糠纸榻B 更多元字符。)
通常 ^ 只匹配字符串的開(kāi)頭,而 $ 只匹配字符串的結(jié)尾,緊接在字符串末尾的換行符(如果有的話(huà))之前。 當(dāng)指定了這個(gè)標(biāo)志時(shí),^ 匹配字符串的開(kāi)頭和字符串中每一行的開(kāi)頭,緊跟在每個(gè)換行符之后。 類(lèi)似地,$ 元字符匹配字符串的結(jié)尾和每行的結(jié)尾(緊接在每個(gè)換行符之前)。
S
DOTALL
使 '.' 特殊字符匹配任何字符,包括換行符;沒(méi)有這個(gè)標(biāo)志,'.' 將匹配任何字符 除了 換行符。
A
ASCII
使 \w、\W、\b、\B、\s 和 \S 執(zhí)行僅 ASCII 匹配而不是完整匹配 Unicode 匹配。 這僅對(duì) Unicode 模式有意義,并且對(duì)于字節(jié)模式將被忽略。
X
VERBOSE
此標(biāo)志允許你編寫(xiě)更易讀的正則表達(dá)式,方法是為您提供更靈活的格式化方式。 指定此標(biāo)志后,將忽略正則字符串中的空格,除非空格位于字符類(lèi)中或前面帶有未轉(zhuǎn)義的反斜杠;這使你可以更清楚地組織和縮進(jìn)正則。 此標(biāo)志還允許你將注釋放在正則中,引擎將忽略該注釋?zhuān)蛔⑨寴?biāo)記為 '#' 既不是在字符類(lèi)中,也不是在未轉(zhuǎn)義的反斜杠之前。
例如,這里的正則使用 re.VERBOSE;看看閱讀有多容易?:
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)
如果沒(méi)有詳細(xì)設(shè)置,正則將如下所示:
charref = re.compile("(0[0-7]+""|[0-9]+""|x[0-9a-fA-F]+);")
在上面的例子中,Python的字符串文字的自動(dòng)連接已被用于將正則分解為更小的部分,但它仍然比以下使用 re.VERBOSE 版本更難理解。
更多模式能力
到目前為止,我們只介紹了正則表達(dá)式的一部分功能。 在本節(jié)中,我們將介紹一些新的元字符,以及如何使用組來(lái)檢索匹配的文本部分。
更多元字符
我們還沒(méi)有涉及到一些元字符。 其中大部分內(nèi)容將在本節(jié)中介紹。
要討論的其余一些元字符是 零寬度斷言 。 它們不會(huì)使解析引擎在字符串中前進(jìn)一個(gè)字符;相反,它們根本不占用任何字符,只是成功或失敗。例如,\b 是一個(gè)斷言,指明當(dāng)前位置位于字邊界;這個(gè)位置根本不會(huì)被 \b 改變。這意味著永遠(yuǎn)不應(yīng)重復(fù)零寬度斷言,因?yàn)槿绻鼈冊(cè)诮o定位置匹配一次,它們顯然可以無(wú)限次匹配。
|
或者“or”運(yùn)算符。 如果 A 和 B 是正則表達(dá)式,A|B 將匹配任何與 A 或 B 匹配的字符串。 | 具有非常低的優(yōu)先級(jí),以便在交替使用多字符字符串時(shí)使其合理地工作。 Crow|Servo 將匹配 'Crow' 或 'Servo',而不是 'Cro'、'w' 或 'S' 和 'ervo'。
要匹配字面 '|',請(qǐng)使用 \|,或?qū)⑵淅ㄔ谧址?lèi)中,如 [|]。
^
在行的開(kāi)頭匹配。 除非設(shè)置了 MULTILINE 標(biāo)志,否則只會(huì)在字符串的開(kāi)頭匹配。 在 MULTILINE 模式下,這也在字符串中的每個(gè)換行符后立即匹配。
例如,如果你希望僅在行的開(kāi)頭匹配單詞 From,則要使用的正則 ^From。:
>>> print(re.search('^From', 'From Here to Eternity'))>>> print(re.search('^From', 'Reciting From Memory'))None
要匹配字面 '^',使用 \^。
$
匹配行的末尾,定義為字符串的結(jié)尾,或者后跟換行符的任何位置。:
>>> print(re.search('}$', '{block}'))>>> print(re.search('}$', '{block} '))None>>> print(re.search('}$', '{block}\n'))
以匹配字面 '$',使用 \$ 或者將其包裹在一個(gè)字符類(lèi)中,例如 [$]。
\A
僅匹配字符串的開(kāi)頭。 當(dāng)不在 MULTILINE 模式時(shí),\A 和 ^ 實(shí)際上是相同的。 在 MULTILINE 模式中,它們是不同的: \A 仍然只在字符串的開(kāi)頭匹配,但 ^ 可以匹配在換行符之后的字符串內(nèi)的任何位置。
\Z
只匹配字符串尾。
\b
字邊界。 這是一個(gè)零寬度斷言,僅在單詞的開(kāi)頭或結(jié)尾處匹配。 單詞被定義為一個(gè)字母數(shù)字字符序列,因此單詞的結(jié)尾由空格或非字母數(shù)字字符表示。
以下示例僅當(dāng)它是一個(gè)完整的單詞時(shí)匹配 class;當(dāng)它包含在另一個(gè)單詞中時(shí)將不會(huì)匹配。
>>> 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
使用這個(gè)特殊序列時(shí),你應(yīng)該記住兩個(gè)細(xì)微之處。 首先,這是 Python 的字符串文字和正則表達(dá)式序列之間最嚴(yán)重的沖突。 在 Python 的字符串文字中,\b 是退格字符,ASCII 值為8。 如果你沒(méi)有使用原始字符串,那么 Python 會(huì)將 \b 轉(zhuǎn)換為退格,你的正則不會(huì)按照你的預(yù)期匹配。 以下示例與我們之前的正則看起來(lái)相同,但省略了正則字符串前面的 'r'。:
>>> p = re.compile('\bclass\b')>>> print(p.search('no class at all'))None>>> print(p.search('\b' + 'class' + '\b'))
其次,在一個(gè)字符類(lèi)中,這個(gè)斷言沒(méi)有用處,\b 表示退格字符,以便與 Python 的字符串文字兼容。
\B
另一個(gè)零寬度斷言,這與 \b 相反,僅在當(dāng)前位置不在字邊界時(shí)才匹配。
分組
通常,你需要獲取更多信息,而不僅僅是正則是否匹配。 正則表達(dá)式通常用于通過(guò)將正則分成幾個(gè)子組來(lái)解析字符串,這些子組匹配不同的感興趣組件。 例如,RFC-822 標(biāo)題行分為標(biāo)題名稱(chēng)和值,用 ':' 分隔,如下所示:
From: author@example.comUser-Agent: Thunderbird 1.5.0.9 (X11/20061227)MIME-Version: 1.0To: editor@example.com
這可以通過(guò)編寫(xiě)與整個(gè)標(biāo)題行匹配的正則表達(dá)式來(lái)處理,并且具有與標(biāo)題名稱(chēng)匹配的一個(gè)組,以及與標(biāo)題的值匹配的另一個(gè)組。
Groups are marked by the '(', ')' metacharacters. '(' and ')' have much the same meaning as they do in mathematical expressions; they group together the expressions contained inside them, and you can repeat the contents of a group with a quantifier, such as *, +, ?, or {m,n}. For example, (ab)* will match zero or more repetitions of ab.
>>> p = re.compile('(ab)*')>>> print(p.match('ababababab').span())(0, 10)
用 '(',')' 表示的組也捕獲它們匹配的文本的起始和結(jié)束索引;這可以通過(guò)將參數(shù)傳遞給 group()、start()、end() 以及 span()。 組從 0 開(kāi)始編號(hào)。組 0 始終存在;它表示整個(gè)正則,所以 匹配對(duì)象 方法都將組 0 作為默認(rèn)參數(shù)。 稍后我們將看到如何表達(dá)不捕獲它們匹配的文本范圍的組。:
>>> p = re.compile('(a)b')>>> m = p.match('ab')>>> m.group()'ab'>>> m.group(0)'ab'
子組從左到右編號(hào),從 1 向上編號(hào)。 組可以嵌套;要確定編號(hào),只需計(jì)算從左到右的左括號(hào)字符。:
>>> p = re.compile('(a(b)c)d')>>> m = p.match('abcd')>>> m.group(0)'abcd'>>> m.group(1)'abc'>>> m.group(2)'b'
group() 可以一次傳遞多個(gè)組號(hào),在這種情況下,它將返回一個(gè)包含這些組的相應(yīng)值的元組。:
>>> m.group(2,1,2)('b', 'abc', 'b')
groups() 方法返回一個(gè)元組,其中包含所有子組的字符串,從1到最后一個(gè)子組。:
>>> m.groups()('abc', 'b')
模式中的后向引用允許你指定還必須在字符串中的當(dāng)前位置找到先前捕獲組的內(nèi)容。 例如,如果可以在當(dāng)前位置找到組 1 的確切內(nèi)容,則 \1 將成功,否則將失敗。 請(qǐng)記住,Python 的字符串文字也使用反斜杠后跟數(shù)字以允許在字符串中包含任意字符,因此正則中引入反向引用時(shí)務(wù)必使用原始字符串。
例如,以下正則檢測(cè)字符串中的雙字。:
>>> p = re.compile(r'\b(\w+)\s+\1\b')>>> p.search('Paris in the the spring').group()'the the'
像這樣的后向引用通常不僅僅用于搜索字符串 —— 很少有文本格式以這種方式重復(fù)數(shù)據(jù) —— 但是你很快就會(huì)發(fā)現(xiàn)它們?cè)趫?zhí)行字符串替換時(shí) 非常 有用。
非捕獲和命名組
精心設(shè)計(jì)的正則可以使用許多組,既可以捕獲感興趣的子串,也可以對(duì)正則本身進(jìn)行分組和構(gòu)建。 在復(fù)雜的正則中,很難跟蹤組號(hào)。 有兩個(gè)功能可以幫助解決這個(gè)問(wèn)題。 它們都使用常用語(yǔ)法進(jìn)行正則表達(dá)式擴(kuò)展,因此我們首先看一下。
Perl 5 以其對(duì)標(biāo)準(zhǔn)正則表達(dá)式的強(qiáng)大補(bǔ)充而聞名。 對(duì)于這些新功能,Perl 開(kāi)發(fā)人員無(wú)法選擇新的單鍵擊元字符或以 \ 開(kāi)頭的新特殊序列,否則 Perl 的正則表達(dá)式與標(biāo)準(zhǔn)正則容易混淆。 例如,如果他們選擇 & 作為一個(gè)新的元字符,舊的表達(dá)式將假設(shè) & 是一個(gè)普通字符,并且不會(huì)編寫(xiě) \& 或 [&]。
Perl 開(kāi)發(fā)人員選擇的解決方案是使用 (?...) 作為擴(kuò)展語(yǔ)法。 括號(hào)后面緊跟 ? 是一個(gè)語(yǔ)法錯(cuò)誤,因?yàn)?? 沒(méi)有什么可重復(fù)的,所以這樣并不會(huì)帶來(lái)任何兼容性問(wèn)題。 緊跟在 ? 之后的字符表示正在使用的擴(kuò)展語(yǔ)法,所以 (?=foo) 是一種語(yǔ)法(一個(gè)前視斷言)和 (?:foo) 是另一種語(yǔ)法( 包含子表達(dá)式 foo 的非捕獲組)。
Python 支持一些 Perl 的擴(kuò)展,并增加了新的擴(kuò)展語(yǔ)法用于 Perl 的擴(kuò)展語(yǔ)法。 如果在問(wèn)號(hào)之后的第一個(gè)字符為 P,即表明其為 Python 專(zhuān)屬的擴(kuò)展。
現(xiàn)在我們已經(jīng)了解了一般的擴(kuò)展語(yǔ)法,我們可以回到簡(jiǎn)化復(fù)雜正則中組處理的功能。
有時(shí)你會(huì)想要使用組來(lái)表示正則表達(dá)式的一部分,但是對(duì)檢索組的內(nèi)容不感興趣。 你可以通過(guò)使用非捕獲組來(lái)顯式表達(dá)這個(gè)事實(shí): (?:...),你可以用任何其他正則表達(dá)式替換 ...。:
>>> m = re.match("([abc])+", "abc")>>> m.groups()('c',)>>> m = re.match("(?:[abc])+", "abc")>>> m.groups()()
除了你無(wú)法檢索組匹配內(nèi)容的事實(shí)外,非捕獲組的行為與捕獲組完全相同;你可以在里面放任何東西,用重復(fù)元字符重復(fù)它,比如 *,然后把它嵌入其他組(捕獲或不捕獲)。 (?:...) 在修改現(xiàn)有模式時(shí)特別有用,因?yàn)槟憧梢蕴砑有陆M而不更改所有其他組的編號(hào)方式。 值得一提的是,捕獲和非捕獲組之間的搜索沒(méi)有性能差異;兩種形式?jīng)]有一種更快。
更重要的功能是命名組:不是通過(guò)數(shù)字引用它們,而是可以通過(guò)名稱(chēng)引用組。
命名組的語(yǔ)法是Python特定的擴(kuò)展之一: (?P。 name 顯然是該組的名稱(chēng)。 命名組的行為與捕獲組完全相同,并且還將名稱(chēng)與組關(guān)聯(lián)。 處理捕獲組的 匹配對(duì)象 方法都接受按編號(hào)引用組的整數(shù)或包含所需組名的字符串。 命名組仍然是給定的數(shù)字,因此你可以通過(guò)兩種方式檢索有關(guān)組的信息:
>>> p = re.compile(r'(?P\b\w+\b)') >>> m = p.search( '(((( Lots of punctuation )))' )>>> m.group('word')'Lots'>>> m.group(1)'Lots'
此外,你可以通過(guò) groupdict() 將命名分組提取為一個(gè)字典:
>>> m = re.match(r'(?P\w+) (?P \w+)', 'Jane Doe') >>> m.groupdict(){'first': 'Jane', 'last': 'Doe'}
Named groups are handy because they let you use easily remembered names, instead of having to remember numbers. Here’s an example RE from the imaplib module:
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 組。
表達(dá)式中的后向引用語(yǔ)法,例如 (...)\1,指的是組的編號(hào)。 當(dāng)然有一種變體使用組名而不是數(shù)字。 這是另一個(gè) Python 擴(kuò)展: (?P=name) 表示在當(dāng)前點(diǎn)再次匹配名為 name 的組的內(nèi)容。 用于查找雙字的正則表達(dá)式,\b(\w+)\s+\1\b 也可以寫(xiě)為 \b(?P:
>>> p = re.compile(r'\b(?P\w+)\s+(?P=word)\b') >>> p.search('Paris in the the spring').group()'the the'
前視斷言
另一個(gè)零寬斷言是前視斷言。 前視斷言有肯定型和否定型兩種形式,如下所示:
(?=…)
肯定型前視斷言。如果內(nèi)部的表達(dá)式(這里用 ... 來(lái)表示)在當(dāng)前位置可以匹配,則匹配成功,否則匹配失敗。 但是,內(nèi)部表達(dá)式嘗試匹配之后,正則引擎并不會(huì)向前推進(jìn);正則表達(dá)式的其余部分依然會(huì)在斷言開(kāi)始的地方嘗試匹配。
(?!…)
否定型前視斷言。 與肯定型斷言正好相反,如果內(nèi)部表達(dá)式在字符串中的當(dāng)前位置 不 匹配,則成功。
更具體一些,來(lái)看一個(gè)前視的實(shí)用案例。 考慮用一個(gè)簡(jiǎn)單的表達(dá)式來(lái)匹配文件名并將其拆分為基本名稱(chēng)和擴(kuò)展名,以 . 分隔。 例如,在 news.rc 中,news 是基本名稱(chēng),rc 是文件名的擴(kuò)展名。
與此匹配的模式非常簡(jiǎn)單:
.*[.].*$
請(qǐng)注意,. 需要特別處理,因?yàn)樗窃址?,所以它在字符?lèi)中只能匹配特定字符。 還要注意尾隨的 $;添加此項(xiàng)以確保擴(kuò)展名中的所有其余字符串都必須包含在擴(kuò)展名中。 這個(gè)正則表達(dá)式匹配 foo.bar、autoexec.bat、sendmail.cf 和 printers.conf。
現(xiàn)在,考慮使更復(fù)雜一點(diǎn)的問(wèn)題;如果你想匹配擴(kuò)展名不是 bat 的文件名怎么辦? 一些錯(cuò)誤的嘗試:
.*[.][^b].*$ 上面的第一次嘗試試圖通過(guò)要求擴(kuò)展名的第一個(gè)字符不是 b 來(lái)排除 bat。 這是錯(cuò)誤的,因?yàn)槟J揭才c foo.bar 不匹配。
.*[.]([^b]..|.[^a].|..[^t])$
當(dāng)你嘗試通過(guò)要求以下一種情況匹配來(lái)修補(bǔ)第一個(gè)解決方案時(shí),表達(dá)式變得更加混亂:擴(kuò)展的第一個(gè)字符不是 b。 第二個(gè)字符不 a;或者第三個(gè)字符不是 t。 這接受 foo.bar 并拒絕 autoexec.bat,但它需要三個(gè)字母的擴(kuò)展名,并且不接受帶有兩個(gè)字母擴(kuò)展名的文件名,例如 sendmail.cf。 為了解決這個(gè)問(wèn)題,我們會(huì)再次使模式復(fù)雜化。
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
在第三次嘗試中,第二個(gè)和第三個(gè)字母都是可選的,以便允許匹配的擴(kuò)展名短于三個(gè)字符,例如 sendmail.cf。
模式現(xiàn)在變得非常復(fù)雜,這使得它難以閱讀和理解。 更糟糕的是,如果問(wèn)題發(fā)生變化并且你想要將 bat 和 exe 排除為擴(kuò)展,那么該模式將變得更加復(fù)雜和混亂。
否定型前視可以解決所有這些困擾:
.*[.](?!bat$)[^.]*{TX-PL-LABEL}#x60;` 否定型前視意味著:如果表達(dá)式 ``bat 在當(dāng)前位置不能匹配,則可以接著嘗試正則表達(dá)式的其余部分;如果 bat{TX-PL-LABEL}#x60;` 能匹配,則整個(gè)正則表達(dá)式將匹配失敗。 尾隨的 ``{TX-PL-LABEL}#x60;` 是必需的,以確??梢云ヅ涞较?``sample.batch 這樣以 bat 開(kāi)頭的文件名。當(dāng)文件名中有多個(gè)點(diǎn)號(hào)時(shí), [^.]* 可以確保表達(dá)式依然有效。
現(xiàn)在很容易排除另一個(gè)文件擴(kuò)展名;只需在斷言中添加它作為替代。 以下模塊排除以 bat 或 exe:
.*[.](?!bat$|exe$)[^.]*$
修改字符串
到目前為止,我們只是針對(duì)靜態(tài)字符串執(zhí)行搜索。 正則表達(dá)式通常也用于以各種方式修改字符串,使用以下模式方法:
|
方法 / 屬性 |
目的 |
|---|---|
分割字符串
模式的 split() 方法在正則匹配的任何地方拆分字符串,返回一個(gè)片段列表。 它類(lèi)似于 split() 字符串方法,但在分隔符的分隔符中提供了更多的通用性;字符串的 split() 僅支持按空格或固定字符串進(jìn)行拆分。 正如你所期望的那樣,還有一個(gè)模塊級(jí) re.split() 函數(shù)。
.split(string[, maxsplit=0])
通過(guò)正則表達(dá)式的匹配拆分 字符串。 如果在正則中使用捕獲括號(hào),則它們的內(nèi)容也將作為結(jié)果列表的一部分返回。 如果 maxsplit 非零,則最多執(zhí)行 maxsplit 次拆分。
你可以通過(guò)傳遞 maxsplit 的值來(lái)限制分割的數(shù)量。 當(dāng) maxsplit 非零時(shí),將最多進(jìn)行 maxsplit 次拆分,并且字符串的其余部分將作為列表的最后一個(gè)元素返回。 在以下示例中,分隔符是任何非字母數(shù)字字符序列。:
>>> 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().']
有時(shí)你不僅對(duì)分隔符之間的文本感興趣,而且還需要知道分隔符是什么。 如果在正則中使用捕獲括號(hào),則它們的值也將作為列表的一部分返回。 比較以下調(diào)用:
>>> p = re.compile(r'\W+'
當(dāng)前名稱(chēng):創(chuàng)新互聯(lián)Python教程:正則表達(dá)式HOWTO
網(wǎng)頁(yè)地址:http://www.5511xx.com/article/dhspdid.html


咨詢(xún)
建站咨詢(xún)
