新聞中心
正則表達式HOWTO
作者

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


咨詢
建站咨詢
