正則表達(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è)正則匹配 Test
或 TEST
,稍后會(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
、 b
或 c
;這與 [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 |
|
正則中的 |
2 |
|
引擎盡可能多地匹配 |
3 |
失敗 |
引擎嘗試匹配 |
4 |
|
回退一次, |
5 |
失敗 |
再次嘗試匹配 |
6 |
|
再次回退,所以 |
6 |
|
再試一次 |
正則現(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.
你可以省略 m 或 n; 在這種情況下,將假定缺失值的合理值。 省略 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ò)展模塊,就類似于 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è)反斜杠。
字符 |
階段 |
---|---|
|
被匹配的字符串 |
|
為 |
|
為字符串字面轉(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ī)字符串 |
原始字符串 |
---|---|
|
|
|
|
|
|
應(yīng)用匹配?
一旦你有一個(gè)表示編譯正則表達(dá)式的對(duì)象,你用它做什么? 模式對(duì)象有幾種方法和屬性。 這里只介紹最重要的內(nèi)容;請(qǐng)參閱 re
文檔獲取完整列表。
方法 / 屬性 |
目的 |
---|---|
|
確定正則是否從字符串的開(kāi)頭匹配。 |
|
掃描字符串,查找此正則匹配的任何位置。 |
|
找到正則匹配的所有子字符串,并將它們作為列表返回。 |
|
找到正則匹配的所有子字符串,并將它們返回為一個(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è)方法和屬性;最重要的是:
方法 / 屬性 |
目的 |
---|---|
|
返回正則匹配的字符串 |
|
返回匹配的開(kāi)始位置 |
|
返回匹配的結(jié)束位置 |
|
返回包含匹配 (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è)置 I
和 M
標(biāo)志。
這是一個(gè)可用標(biāo)志表,以及每個(gè)標(biāo)志的更詳細(xì)說(shuō)明。
旗標(biāo) |
含意 |
---|---|
|
使幾個(gè)轉(zhuǎn)義如 |
|
使 |
|
進(jìn)行大小寫(xiě)不敏感匹配。 |
|
進(jìn)行區(qū)域設(shè)置感知匹配。 |
|
多行匹配,影響 |
|
啟用詳細(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)算符。 如果 A 和 B 是正則表達(dá)式,
A|B
將匹配任何與 A 或 B 匹配的字符串。|
具有非常低的優(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.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ù)雜和混亂。
負(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ò)展名;只需在斷言中添加它作為替代。 以下模塊排除以 bat
或 exe
:
.*[.](?!bat$|exe$)[^.]*$
修改字符串?
到目前為止,我們只是針對(duì)靜態(tài)字符串執(zhí)行搜索。 正則表達(dá)式通常也用于以各種方式修改字符串,使用以下模式方法:
方法 / 屬性 |
目的 |
---|---|
|
將字符串拆分為一個(gè)列表,在正則匹配的任何地方將其拆分 |
|
找到正則匹配的所有子字符串,并用不同的字符串替換它們 |
|
與 |
分割字符串?
模式的 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)題。
match() 和 search()?
The match()
function only checks if the RE matches at the beginning of the
string while search()
will scan forward through the string for a match.
It's important to keep this distinction in mind. Remember, match()
will
only report a successful match which will start at 0; if the match wouldn't
start at zero, match()
will not report it.
>>> print(re.match('super', 'superstition').span())
(0, 5)
>>> print(re.match('super', 'insuperable'))
None
另一方面, search()
將向前掃描字符串,報(bào)告它找到的第一個(gè)匹配項(xiàng)。:
>>> print(re.search('super', 'superstition').span())
(0, 5)
>>> print(re.search('super', 'insuperable').span())
(2, 7)
有時(shí)你會(huì)被誘惑繼續(xù)使用 re.match()
,只需在你的正則前面添加 .*
。抵制這種誘惑并使用 re.search()
代替。 正則表達(dá)式編譯器對(duì)正則進(jìn)行一些分析,以加快尋找匹配的過(guò)程。 其中一個(gè)分析可以確定匹配的第一個(gè)特征必須是什么;例如,以 Crow
開(kāi)頭的模式必須與 'C'
匹配。 分析讓引擎快速掃描字符串,尋找起始字符,只在找到 'C'
時(shí)嘗試完全匹配。
添加 .*
會(huì)使這個(gè)優(yōu)化失效,需要掃描到字符串的末尾,然后回溯以找到正則的其余部分的匹配。 使用 re.search()
代替。
貪婪與非貪婪?
當(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ū)館中查找它。