正則表達(dá)式HOWTO?

作者

A.M. Kuchling <amk@amk.ca>

摘要

本文檔是在Python中使用 re 模塊使用正則表達(dá)式的入門教程。 它提供了比“標(biāo)準(zhǔn)庫(kù)參考”中相應(yīng)部分更平和的介紹。

概述?

正則表達(dá)式(稱為RE,或正則,或正則表達(dá)式模式)本質(zhì)上是嵌入在Python中的一種微小的、高度專業(yè)化的編程語(yǔ)言,可通過(guò) re 模塊獲得。 使用這種小語(yǔ)言,你可以為要匹配的可能字符串集指定規(guī)則;此集可能包含英語(yǔ)句子,電子郵件地址,TeX命令或你喜歡的任何內(nèi)容。 然后,您可以詢問(wèn)諸如“此字符串是否與模式匹配?”或“此字符串中的模式是否匹配?”等問(wèn)題。 你還可以使用正則修改字符串或以各種方式將其拆分。

正則表達(dá)式模式被編譯成一系列字節(jié)碼,然后由用 C 編寫(xiě)的匹配引擎執(zhí)行。對(duì)于高級(jí)用途,可能需要特別注意引擎如何執(zhí)行給定的正則,并將正則寫(xiě)入以某種方式生成運(yùn)行速度更快的字節(jié)碼。 本文檔未涉及優(yōu)化,因?yàn)樗竽愠浞至私馄ヅ湟娴膬?nèi)部結(jié)構(gòu)。

正則表達(dá)式語(yǔ)言相對(duì)較小且受限制,因此并非所有可能的字符串處理任務(wù)都可以使用正則表達(dá)式完成。 還有一些任務(wù) 可以 用正則表達(dá)式完成,但表達(dá)式變得非常復(fù)雜。 在這些情況下,你最好編寫(xiě) Python 代碼來(lái)進(jìn)行處理;雖然 Python 代碼比精心設(shè)計(jì)的正則表達(dá)式慢,但它也可能更容易理解。

簡(jiǎn)單模式?

我們首先要了解最簡(jiǎn)單的正則表達(dá)式。 由于正則表達(dá)式用于對(duì)字符串進(jìn)行操作,因此我們將從最常見(jiàn)的任務(wù)開(kāi)始:匹配字符。

有關(guān)正則表達(dá)式(確定性和非確定性有限自動(dòng)機(jī))的計(jì)算機(jī)科學(xué)的詳細(xì)解釋,你可以參考幾乎所有有關(guān)編寫(xiě)編譯器的教科書(shū)。

匹配字符?

大多數(shù)字母和字符只會(huì)匹配自己。 例如,正則表達(dá)式 test 將完全匹配字符串 test 。 (你可以啟用一個(gè)不區(qū)分大小寫(xiě)的模式,讓這個(gè)正則匹配 TestTEST,稍后會(huì)詳細(xì)介紹。)

這條規(guī)則有例外;一些字符是特殊的 metacharacters ,并且不匹配自己。 相反,它們表示應(yīng)該匹配一些與眾不同的東西,或者通過(guò)重復(fù)它們或改變它們的含義來(lái)影響正則的其他部分。 本文檔的大部分內(nèi)容都致力于討論各種元字符及其功能。

這是元字符的完整列表;它們的意思將在本HOWTO的其余部分討論。

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

我們將看到的第一個(gè)元字符是 [] 。 它們用于指定字符類,它是你希望匹配的一組字符。 可以單獨(dú)列出字符,也可以通過(guò)給出兩個(gè)字符并用 '-' 標(biāo)記將它們分開(kāi)來(lái)表示一系列字符。 例如, [abc] 將匹配任何字符 a、 bc ;這與 [a-c] 相同,它使用一個(gè)范圍來(lái)表示同一組字符。 如果你只想匹配小寫(xiě)字母,你的正則是 [a-z] 。

Metacharacters (except \) are not active inside classes. For example, [akm$] will match any of the characters 'a', 'k', 'm', or '$'; '$' is usually a metacharacter, but inside a character class it's stripped of its special nature.

你可以通過(guò)以下方式匹配 complementing 設(shè)置的字符類中未列出的字符。這通過(guò)包含一個(gè) '^' 作為該類的第一個(gè)字符來(lái)表示。 例如,[^5] 將匹配除 '5' 之外的任何字符。 如果插入符出現(xiàn)在字符類的其他位置,則它沒(méi)有特殊含義。 例如:[5^] 將匹配 '5''^'。

也許最重要的元字符是反斜杠,\。 與 Python 字符串文字一樣,反斜杠后面可以跟各種字符,以指示各種特殊序列。它也用于轉(zhuǎn)義所有元字符,因此您仍然可以在模式中匹配它們;例如,如果你需要匹配 [\,你可以在它們前面加一個(gè)反斜杠來(lái)移除它們的特殊含義:\[\\。

一些以 '\' 開(kāi)頭的特殊序列表示通常有用的預(yù)定義字符集,例如數(shù)字集、字母集或任何非空格的集合。

讓我們舉一個(gè)例子:\w 匹配任何字母數(shù)字字符。 如果正則表達(dá)式模式以字節(jié)類表示,這相當(dāng)于類 [a-zA-Z0-9_]。如果正則表達(dá)式是一個(gè)字符串,\w 將匹配由 unicodedata 模塊提供的 Unicode 數(shù)據(jù)庫(kù)中標(biāo)記為字母的所有字符。 通過(guò)在編譯正則表達(dá)式時(shí)提供 re.ASCII 標(biāo)志,可以在字符串模式中使用更為受限制的 \w 定義。

以下特殊序列列表不完整。 有關(guān) Unicode 字符串模式的序列和擴(kuò)展類定義的完整列表,請(qǐng)參閱標(biāo)準(zhǔn)庫(kù)參考中的最后一部分 正則表達(dá)式語(yǔ)法 。通常,Unicode 版本匹配 Unicode 數(shù)據(jù)庫(kù)中相應(yīng)類別中的任何字符。

\d

匹配任何十進(jìn)制數(shù)字;這等價(jià)于類 [0-9]。

\D

匹配任何非數(shù)字字符;這等價(jià)于類 [^0-9]。

\s

匹配任何空白字符;這等價(jià)于類 [ \t\n\r\f\v]

\S

匹配任何非空白字符;這相當(dāng)于類 [^ \t\n\r\f\v]

\w

匹配任何字母與數(shù)字字符;這相當(dāng)于類 [a-zA-Z0-9_]。

\W

匹配任何非字母與數(shù)字字符;這相當(dāng)于類 [^a-zA-Z0-9_]。

這些序列可以包含在字符類中。 例如,[\s,.] 是一個(gè)匹配任何空格字符的字符類或者 ',' ,或 '.'。

本節(jié)的最后一個(gè)元字符是 . 。 它匹配除換行符之外的任何內(nèi)容,并且有一個(gè)可選模式( re.DOTALL )甚至可以匹配換行符。 . 常用于你想匹配“任何字符”的地方。

重復(fù)?

能夠匹配不同的字符集合是正則表達(dá)式可以做的第一件事,這對(duì)于字符串可用方法來(lái)說(shuō)是不可能的。 但是,如果這是正則表達(dá)式的唯一額外功能,那么它們就不會(huì)有太大的優(yōu)勢(shì)。 另一個(gè)功能是你可以指定正則的某些部分必須重復(fù)一定次數(shù)。

重復(fù)中我們要了解的第一個(gè)元字符是 *。 * 與字面字符 '*' 不匹配;相反,它指定前一個(gè)字符可以匹配零次或多次,而不是恰好一次。

例如,ca*t 將匹配 'ct' (0個(gè) 'a' 字符),'cat' (1個(gè) 'a' ), 'caaat' (3個(gè) 'a' 字符),等等。

類似 * 這樣的重復(fù)是 貪婪的;當(dāng)重復(fù)正則時(shí),匹配引擎將嘗試盡可能多地重復(fù)它。 如果模式的后續(xù)部分不匹配,則匹配引擎將回退并以較少的重復(fù)次數(shù)再次嘗試。

一個(gè)逐步的例子將使這更加明顯。 讓我們考慮表達(dá)式 a[bcd]*b。 這個(gè)正則匹配字母 'a',類 [bcd] 中的零或多個(gè)字母,最后以 'b' 結(jié)尾。 現(xiàn)在想象一下這個(gè)正則與字符串 'abcbd' 匹配。

步驟

匹配

說(shuō)明

1

a

正則中的 a 匹配。

2

abcbd

引擎盡可能多地匹配 [bcd]* ,直到字符串結(jié)束。

3

失敗

引擎嘗試匹配 b ,但是當(dāng)前位置位于字符串結(jié)束,所以匹配失敗。

4

abcb

回退一次,[bcd]* 少匹配一個(gè)字符。

5

失敗

再次嘗試匹配 b , 但是當(dāng)前位置是最后一個(gè)字符 'd' 。

6

abc

再次回退,所以 [bcd]* 只匹配 bc

6

abcb

再試一次 b 。 這次當(dāng)前位置的字符是 'b' ,所以它成功了。

正則現(xiàn)在已經(jīng)結(jié)束了,它已經(jīng)匹配了 'abcb'。 這演示了匹配引擎最初如何進(jìn)行,如果沒(méi)有找到匹配,它將逐步回退并一次又一次地重試正則的其余部分。 它將回退,直到它為 [bcd]* 嘗試零匹配,如果隨后失敗,引擎將斷定該字符串與正則完全不匹配。

另一個(gè)重復(fù)的元字符是 +,它匹配一次或多次。 要特別注意 *+ 之間的區(qū)別;* 匹配 零次 或更多次,因此重復(fù)的任何東西都可能根本不存在,而 + 至少需要 一次。 使用類似的例子,ca+t 將匹配 'cat' (1 個(gè) 'a'),'caaat' (3 個(gè) 'a'),但不會(huì)匹配 '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.

你可以省略 mn; 在這種情況下,將假定缺失值的合理值。 省略 m 被解釋為 0 下限,而省略 n 則為無(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ì)象,然后用它們進(jìn)行匹配。

編譯正則表達(dá)式?

正則表達(dá)式被編譯成模式對(duì)象,模式對(duì)象具有各種操作的方法,例如搜索模式匹配或執(zhí)行字符串替換。:

>>>
>>> import re
>>> p = re.compile('ab*')
>>> p
re.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ò)展模塊,就類似于 socketzlib 模塊。

將正則放在字符串中可以使 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è)反斜杠。

字符

階段

\section

被匹配的字符串

\\section

re.compile() 轉(zhuǎn)義的反斜杠

"\\\\section"

為字符串字面轉(zhuǎn)義的反斜杠

簡(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ī)字符串

原始字符串

"ab*"

r"ab*"

"\\\\section"

r"\\section"

"\\w+\\s+\\1"

r"\w+\s+\1"

應(yīng)用匹配?

一旦你有一個(gè)表示編譯正則表達(dá)式的對(duì)象,你用它做什么? 模式對(duì)象有幾種方法和屬性。 這里只介紹最重要的內(nèi)容;請(qǐng)參閱 re 文檔獲取完整列表。

方法 / 屬性

目的

match()

確定正則是否從字符串的開(kāi)頭匹配。

search()

掃描字符串,查找此正則匹配的任何位置。

findall()

找到正則匹配的所有子字符串,并將它們作為列表返回。

finditer()

找到正則匹配的所有子字符串,并將它們返回為一個(gè) iterator。

如果沒(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]+')
>>> p
re.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
<re.Match object; span=(0, 5), match='tempo'>

現(xiàn)在你可以檢查 匹配對(duì)象 以獲取有關(guān)匹配字符串的信息。 匹配對(duì)象實(shí)例也有幾個(gè)方法和屬性;最重要的是:

方法 / 屬性

目的

group()

返回正則匹配的字符串

start()

返回匹配的開(kāi)始位置

end()

返回匹配的結(jié)束位置

span()

返回包含匹配 (start, end) 位置的元組

嘗試這些方法很快就會(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)
<re.Match object; span=(4, 11), match='message'>
>>> 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  
<callable_iterator object at 0x...>
>>> 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')  
<re.Match object; span=(0, 5), match='From '>

本質(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)中訪問(wèn)正則表達(dá)式,預(yù)編譯它將節(jié)省一些函數(shù)調(diào)用。 在循環(huán)之外,由于有內(nèi)部緩存,沒(méi)有太大區(qū)別。

編譯標(biāo)志?

編譯標(biāo)志允許你修改正則表達(dá)式的工作方式。 標(biāo)志在 re 模塊中有兩個(gè)名稱,長(zhǎ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è)置 IM 標(biāo)志。

這是一個(gè)可用標(biāo)志表,以及每個(gè)標(biāo)志的更詳細(xì)說(shuō)明。

旗標(biāo)

含意

ASCII, A

使幾個(gè)轉(zhuǎn)義如 \w\b、\s\d 匹配僅與具有相應(yīng)特征屬性的 ASCII 字符匹配。

DOTALL, S

使 . 匹配任何字符,包括換行符。

IGNORECASE, I

進(jìn)行大小寫(xiě)不敏感匹配。

LOCALE, L

進(jìn)行區(qū)域設(shè)置感知匹配。

MULTILINE, M

多行匹配,影響 ^$。

VERBOSE, X (為 '擴(kuò)展')

啟用詳細(xì)的正則,可以更清晰,更容易理解。

I
IGNORECASE

執(zhí)行不區(qū)分大小寫(xiě)的匹配;字符類和字面字符串將通過(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ě)敏感匹配依賴于當(dāng)前區(qū)域而不是 Unicode 數(shù)據(jù)庫(kù)。

區(qū)域設(shè)置是 C 庫(kù)的一個(gè)功能,旨在幫助編寫(xiě)考慮到語(yǔ)言差異的程序。例如,如果你正在處理編碼的法語(yǔ)文本,那么你希望能夠編寫(xiě) \w+ 來(lái)匹配單詞,但 \w 只匹配字符類 [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)有解釋;它們將在以下部分介紹 更多元字符。)

通常 ^ 只匹配字符串的開(kāi)頭,而 $ 只匹配字符串的結(jié)尾,緊接在字符串末尾的換行符(如果有的話)之前。 當(dāng)指定了這個(gè)標(biāo)志時(shí),^ 匹配字符串的開(kāi)頭和字符串中每一行的開(kāi)頭,緊跟在每個(gè)換行符之后。 類似地,$ 元字符匹配字符串的結(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)志后,將忽略正則字符串中的空格,除非空格位于字符類中或前面帶有未轉(zhuǎn)義的反斜杠;這使你可以更清楚地組織和縮進(jìn)正則。 此標(biāo)志還允許你將注釋放在正則中,引擎將忽略該注釋;注釋標(biāo)記為 '#' 既不是在字符類中,也不是在未轉(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)算符。 如果 AB 是正則表達(dá)式,A|B 將匹配任何與 AB 匹配的字符串。 | 具有非常低的優(yōu)先級(jí),以便在交替使用多字符字符串時(shí)使其合理地工作。 Crow|Servo 將匹配 'Crow''Servo',而不是 'Cro'、'w''S''ervo'。

要匹配字面 '|',請(qǐng)使用 \|,或?qū)⑵淅ㄔ谧址愔校?[|]。

^

在行的開(kāi)頭匹配。 除非設(shè)置了 MULTILINE 標(biāo)志,否則只會(huì)在字符串的開(kāi)頭匹配。 在 MULTILINE 模式下,這也在字符串中的每個(gè)換行符后立即匹配。

例如,如果你希望僅在行的開(kāi)頭匹配單詞 From,則要使用的正則 ^From。:

>>>
>>> print(re.search('^From', 'From Here to Eternity'))  
<re.Match object; span=(0, 4), match='From'>
>>> print(re.search('^From', 'Reciting From Memory'))
None

要匹配字面 '^',使用 \^。

$

匹配行的末尾,定義為字符串的結(jié)尾,或者后跟換行符的任何位置。:

>>>
>>> print(re.search('}$', '{block}'))  
<re.Match object; span=(6, 7), match='}'>
>>> print(re.search('}$', '{block} '))
None
>>> print(re.search('}$', '{block}\n'))  
<re.Match object; span=(6, 7), match='}'>

以匹配字面 '$',使用 \$ 或者將其包裹在一個(gè)字符類中,例如 [$]。

\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'))
<re.Match object; span=(3, 8), match='class'>
>>> 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'))
<re.Match object; span=(0, 7), match='\x08class\x08'>

其次,在一個(gè)字符類中,這個(gè)斷言沒(méi)有用處,\b 表示退格字符,以便與 Python 的字符串文字兼容。

\B

另一個(gè)零寬度斷言,這與 \b 相反,僅在當(dāng)前位置不在字邊界時(shí)才匹配。

分組?

通常,你需要獲取更多信息,而不僅僅是正則是否匹配。 正則表達(dá)式通常用于通過(guò)將正則分成幾個(gè)子組來(lái)解析字符串,這些子組匹配不同的感興趣組件。 例如,RFC-822 標(biāo)題行分為標(biāo)題名稱和值,用 ':' 分隔,如下所示:

From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com

這可以通過(guò)編寫(xiě)與整個(gè)標(biāo)題行匹配的正則表達(dá)式來(lái)處理,并且具有與標(biāo)題名稱匹配的一個(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ù)的,所以這并沒(méi)有引入任何兼容性問(wèn)題。 緊跟在 ? 之后的字符表示正在使用什么擴(kuò)展名,所以 (?=foo) 是一個(gè)東西(一個(gè)正向的先行斷言)和 (?:foo) 是其它東西( 包含子表達(dá)式 foo 的非捕獲組)。

Python 支持一些 Perl 的擴(kuò)展,并增加了新的擴(kuò)展語(yǔ)法用于 Perl 的擴(kuò)展語(yǔ)法。 如果在問(wèn)號(hào)之后的第一個(gè)字符為 P,即表明其為 Python 專屬的擴(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ò)名稱引用組。

命名組的語(yǔ)法是Python特定的擴(kuò)展之一: (?P<name>...)。 name 顯然是該組的名稱。 命名組的行為與捕獲組完全相同,并且還將名稱與組關(guān)聯(lián)。 處理捕獲組的 匹配對(duì)象 方法都接受按編號(hào)引用組的整數(shù)或包含所需組名的字符串。 命名組仍然是給定的數(shù)字,因此你可以通過(guò)兩種方式檢索有關(guān)組的信息:

>>>
>>> p = re.compile(r'(?P<word>\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<first>\w+) (?P<last>\w+)', 'Jane Doe')
>>> m.groupdict()
{'first': 'Jane', 'last': 'Doe'}

命名組很有用,因?yàn)樗鼈冊(cè)试S你使用容易記住的名稱,而不必記住數(shù)字。 這是來(lái)自 imaplib 模塊的示例正則

InternalDate = re.compile(r'INTERNALDATE "'
        r'(?P<day>[ 123][0-9])-(?P<mon>[A-Z][a-z][a-z])-'
        r'(?P<year>[0-9][0-9][0-9][0-9])'
        r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
        r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[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<word>\w+)\s+(?P=word)\b:

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

前向斷言?

另一個(gè)零寬度斷言是前向斷言。 前向斷言以正面和負(fù)面形式提供,如下所示:

(?=…)

正向前向斷言。 如果包含的正則表達(dá)式,由 ... 表示,在當(dāng)前位置成功匹配,則成功,否則失敗。 但是,一旦嘗試了包含的表達(dá)式,匹配的引擎就不會(huì)前進(jìn);模式其余的部分會(huì)在在斷言開(kāi)始的地方嘗試。

(?!…)

負(fù)向前向斷言。 這與積正向斷言相反;如果包含的表達(dá)式在字符串中的當(dāng)前位置 匹配,則成功。

更具體一些,讓我們看看前向是有用的情況。 考慮一個(gè)簡(jiǎn)單的模式來(lái)匹配文件名并將其拆分為基本名稱和擴(kuò)展名,用 . 分隔。 例如,在 news.rc 中,news 是基本名稱,rc 是文件名的擴(kuò)展名。

與此匹配的模式非常簡(jiǎn)單:

.*[.].*$

請(qǐng)注意,. 需要特別處理,因?yàn)樗窃址?,所以它在字符類中只能匹配特定字符?還要注意尾隨的 $;添加此項(xiàng)以確保擴(kuò)展名中的所有其余字符串都必須包含在擴(kuò)展名中。 這個(gè)正則表達(dá)式匹配 foo.barautoexec.bat、sendmail.cfprinters.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ā)生變化并且你想要將 batexe 排除為擴(kuò)展,那么該模式將變得更加復(fù)雜和混亂。

負(fù)面前向消除了所有這些困擾:

.*[.](?!bat$)[^.]*$ 負(fù)向前向意味著:如果表達(dá)式 bat 此時(shí)不匹配,請(qǐng)嘗試其余的模式;如果 bat$ 匹配,整個(gè)模式將失敗。 尾隨的 $ 是必需的,以確保允許像 sample.batch 這樣的擴(kuò)展只以 bat 開(kāi)頭的文件能通過(guò)。 [^.]* 確保當(dāng)文件名中有多個(gè)點(diǎn)時(shí),模式有效。

現(xiàn)在很容易排除另一個(gè)文件擴(kuò)展名;只需在斷言中添加它作為替代。 以下模塊排除以 batexe:

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

修改字符串?

到目前為止,我們只是針對(duì)靜態(tài)字符串執(zhí)行搜索。 正則表達(dá)式通常也用于以各種方式修改字符串,使用以下模式方法:

方法 / 屬性

目的

split()

將字符串拆分為一個(gè)列表,在正則匹配的任何地方將其拆分

sub()

找到正則匹配的所有子字符串,并用不同的字符串替換它們

subn()

sub() 相同,但返回新字符串和替換次數(shù)

分割字符串?

模式的 split() 方法在正則匹配的任何地方拆分字符串,返回一個(gè)片段列表。 它類似于 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+')
>>> 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', '.', '']

模塊級(jí)函數(shù) re.split() 添加要正則作為第一個(gè)參數(shù),但在其他方面是相同的。:

>>>
>>> re.split(r'[\W]+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'([\W]+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'[\W]+', 'Words, words, words.', 1)
['Words', 'words, words.']

搜索和替換?

另一個(gè)常見(jiàn)任務(wù)是找到模式的所有匹配項(xiàng),并用不同的字符串替換它們。 sub() 方法接受一個(gè)替換值,可以是字符串或函數(shù),也可以是要處理的字符串。

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

返回通過(guò)替換 replacement 替換 string 中正則的最左邊非重疊出現(xiàn)而獲得的字符串。 如果未找到模式,則 string 將保持不變。

可選參數(shù) count 是要替換的模式最大的出現(xiàn)次數(shù);count 必須是非負(fù)整數(shù)。 默認(rèn)值 0 表示替換所有。

這是一個(gè)使用 sub() 方法的簡(jiǎn)單示例。 它用 colour 這個(gè)詞取代顏色名稱:

>>>
>>> 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() 方法完成相同的工作,但返回一個(gè)包含新字符串值和已執(zhí)行的替換次數(shù)的 2 元組:

>>>
>>> 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)

僅當(dāng)空匹配與前一個(gè)空匹配不相鄰時(shí),才會(huì)替換空匹配。:

>>>
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'

如果 replacement 是一個(gè)字符串,則處理其中的任何反斜杠轉(zhuǎn)義。 也就是說(shuō),\n 被轉(zhuǎn)換為單個(gè)換行符,\r 被轉(zhuǎn)換為回車符,依此類推。 諸如 \& 之類的未知轉(zhuǎn)義是孤立的。 后向引用,例如 \6,被替換為正則中相應(yīng)組匹配的子字符串。 這使你可以在生成的替換字符串中合并原始文本的部分內(nèi)容。

這個(gè)例子匹配單詞 section 后跟一個(gè)用 {,} 括起來(lái)的字符串,并將 section 改為 subsection

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

還有一種語(yǔ)法用于引用由 (?P<name>...) 語(yǔ)法定義的命名組。 \g<name> 將使用名為 name 的組匹配的子字符串,\g<number> 使用相應(yīng)的組號(hào)。 因此 \g<2> 等同于 \2,但在諸如 \g<2>0 之類的替換字符串中并不模糊。 (\20 將被解釋為對(duì)組 20 的引用,而不是對(duì)組 2 的引用,后跟字面字符 '0'。) 以下替換都是等效的,但使用所有三種變體替換字符串。:

>>>
>>> p = re.compile('section{ (?P<name> [^}]* ) }', 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<name>}','section{First}')
'subsection{First}'

replacement 也可以是一個(gè)函數(shù),它可以為你提供更多控制。 如果 replacement 是一個(gè)函數(shù),則為 pattern 的每次非重疊出現(xiàn)將調(diào)用該函數(shù)。 在每次調(diào)用時(shí),函數(shù)都會(huì)傳遞一個(gè)匹配的 匹配對(duì)象 參數(shù),并可以使用此信息計(jì)算所需的替換字符串并將其返回。

在以下示例中,替換函數(shù)將小數(shù)轉(zhuǎn)換為十六進(jìn)制:

>>>
>>> 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.'

使用模塊級(jí)別 re.sub() 函數(shù)時(shí),模式作為第一個(gè)參數(shù)傳遞。 模式可以是對(duì)象或字符串;如果需要指定正則表達(dá)式標(biāo)志,則必須使用模式對(duì)象作為第一個(gè)參數(shù),或者在模式字符串中使用嵌入式修飾符,例如: sub("(?i)b+", "x", "bbbb BBBB") 返回 'x x'。

常見(jiàn)問(wèn)題?

正則表達(dá)式對(duì)于某些應(yīng)用程序來(lái)說(shuō)是一個(gè)強(qiáng)大的工具,但在某些方面,它們的行為并不直觀,有時(shí)它們的行為方式與你的預(yù)期不同。 本節(jié)將指出一些最常見(jiàn)的陷阱。

使用字符串方法?

有時(shí)使用 re 模塊是一個(gè)錯(cuò)誤。 如果你匹配固定字符串或單個(gè)字符類,并且你沒(méi)有使用任何 re 功能,例如 IGNORECASE 標(biāo)志,那么正則表達(dá)式的全部功能可能不是必需的。 字符串有幾種方法可以使用固定字符串執(zhí)行操作,它們通常要快得多,因?yàn)閷?shí)現(xiàn)是一個(gè)針對(duì)此目的而優(yōu)化的單個(gè)小 C 循環(huán),而不是大型、更通用的正則表達(dá)式引擎。

一個(gè)例子可能是用另一個(gè)固定字符串替換一個(gè)固定字符串;例如,你可以用 deed 替換 word 。 re.sub() 看起來(lái)像是用于此的函數(shù),但請(qǐng)考慮 replace() 方法。 注意 replace() 也會(huì)替換單詞里面的 word ,把 swordfish 變成 sdeedfish ,但簡(jiǎn)單的正則 word 也會(huì)這樣做。 (為了避免對(duì)單詞的部分進(jìn)行替換,模式必須是 \bword\b,以便要求 word 在任何一方都有一個(gè)單詞邊界。這使得工作超出了 replace() 的能力。)

另一個(gè)常見(jiàn)任務(wù)是從字符串中刪除單個(gè)字符的每個(gè)匹配項(xiàng)或?qū)⑵涮鎿Q為另一個(gè)字符。 你可以用 re.sub('\n', ' ', S) 之類的東西來(lái)做這件事,但是 translate() 能夠完成這兩項(xiàng)任務(wù),并且比任何正則表達(dá)式都快。

簡(jiǎn)而言之,在轉(zhuǎn)向 re 模塊之前,請(qǐng)考慮是否可以使用更快更簡(jiǎn)單的字符串方法解決問(wèn)題。

貪婪與非貪婪?

當(dāng)重復(fù)一個(gè)正則表達(dá)式時(shí),就像在 a* 中一樣,最終的動(dòng)作就是消耗盡可能多的模式。 當(dāng)你嘗試匹配一對(duì)對(duì)稱分隔符,例如 HTML 標(biāo)記周圍的尖括號(hào)時(shí),這個(gè)事實(shí)經(jīng)常會(huì)讓你感到困惑。因?yàn)?.* 的貪婪性質(zhì), 用于匹配單個(gè) HTML 標(biāo)記的簡(jiǎn)單模式不起作用。

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

正則匹配 '<' 中的 '<html>'.* 消耗字符串的其余部分。 正則中還有更多的剩余東西,并且 > 在字符串的末尾不能匹配,所以正則表達(dá)式引擎必須逐個(gè)字符地回溯,直到它找到匹配 > 。最終匹配從 '<html>' 中的 '<' 擴(kuò)展到 '</title>' 中的 '>' ,而這并不是你想要的結(jié)果。

In this case, the solution is to use the non-greedy quantifiers *?, +?, ??, or {m,n}?, which match as little text as possible. In the above example, the '>' is tried immediately after the first '<' matches, and when it fails, the engine advances a character at a time, retrying the '>' at every step. This produces just the right result:

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

(請(qǐng)注意,使用正則表達(dá)式解析 HTML 或 XML 很痛苦??於K的模式將處理常見(jiàn)情況,但 HTML 和 XML 有特殊情況會(huì)破壞明顯的正則表達(dá)式;當(dāng)你編寫(xiě)正則表達(dá)式處理所有可能的情況時(shí),模式將非常復(fù)雜。使用 HTML 或 XML 解析器模塊來(lái)執(zhí)行此類任務(wù)。)

使用 re.VERBOSE?

到目前為止,你可能已經(jīng)注意到正則表達(dá)式是一種非常緊湊的表示法,但它們并不是非常易讀。 具有中等復(fù)雜度的正則可能會(huì)成為反斜杠、括號(hào)和元字符的冗長(zhǎng)集合,使其難以閱讀和理解。

對(duì)于這樣的正則,在編譯正則表達(dá)式時(shí)指定 re.VERBOSE 標(biāo)志可能會(huì)有所幫助,因?yàn)樗试S你更清楚地格式化正則表達(dá)式。

re.VERBOSE 標(biāo)志有幾種效果。 正則表達(dá)式中的 不是 在字符類中的空格將被忽略。 這意味著表達(dá)式如 dog | cat 等同于不太可讀的 dog|cat ,但 [a b] 仍將匹配字符 'a' 、 'b' 或空格。 此外,你還可以在正則中放置注釋;注釋從 # 字符擴(kuò)展到下一個(gè)換行符。 當(dāng)與三引號(hào)字符串一起使用時(shí),這使正則的格式更加整齊:

pat = re.compile(r"""
 \s*                 # Skip leading whitespace
 (?P<header>[^:]+)   # Header name
 \s* :               # Whitespace, and a colon
 (?P<value>.*?)      # 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<header>[^:]+)\s*:(?P<value>.*?)\s*$")

反饋?

正則表達(dá)式是一個(gè)復(fù)雜的主題。 這份文檔是否有助于你理解它們? 是否存在不清楚的部分,或者你遇到的問(wèn)題未在此處涉及? 如果是,請(qǐng)向作者發(fā)送改進(jìn)建議。

關(guān)于正則表達(dá)式的最完整的書(shū)幾乎肯定是由 O'Reilly 出版的 Jeffrey Friedl 的 Mastering Regular Expressions 。 不幸的是,它專注于 Perl 和 Java 的正則表達(dá)式,并且根本不包含任何 Python 材料,因此它不能用作 Python 編程的參考。 (第一版涵蓋了 Python 現(xiàn)在刪除的 regex 模塊,這對(duì)你沒(méi)有多大幫助。)考慮從你的圖書(shū)館中查找它。