1. 程式人生 > >Python 3.7.1 模組 正則表示式 re

Python 3.7.1 模組 正則表示式 re

正則表示式操作


原始碼: Lib/re.py
翻譯+自己的理解

此模組提供與Perl中類似的正則表示式匹配操作。

要搜尋的模式和字串都可以是Unicode字串(str)以及8-bit 字串(bytes)。但是,不能混合Unicode字串和8-bit 字串:也就是說,您不能將Unicode字串與位元組模式匹配,反之亦然; 類似地,當要求替換時,替換字串必須與模式和搜尋字串具有相同的型別。

正則表示式使用反斜槓字元(\)來表示特殊形式或允許使用特殊字元而不呼叫它們的特殊含義。這與Python對 字串文字 中相同用途的相同字元的使用不同; 例如,要匹配文字反斜槓,必須將其模式字串寫為\\\\,因為正則表示式必須是\\,並且在常規Python字串文字中每個反斜槓必須表示為\\

解決方案是使用Python的原始字串表示法來表示正則表示式模式; 在r字首的字串文字中,不以任何特殊方式處理反斜槓。r"\n"是一個包含\n的雙字元字串 ,同時\n是包含換行符的單字元字串。通常在Python程式碼中,模式將使用此原始字串表示法表示。

值得注意的是,大多數正則表示式操作都可用作 編譯正則表示式 的模組級函式和方法 。這些函式是很便捷,不需要先編譯正則表示式物件,但會遺漏一些微調引數。

擴充套件
第三方 正則 表示式模組,它具有與標準庫re 模組 相容的API ,但提供了額外的功能和更全面的Unicode支援。

1. 正則表示式語法

正則表示式(或RE)指定一組與之匹配的字串; 通過此模組中的函式,您可以檢查特定字串是否與給定的正則表示式匹配(或者如果給定的正則表示式與特定字串匹配,則歸結為同一個字串)。

正則表示式可以連線起來形成新的正則表示式; 如果A 和B都是正則表示式,那麼AB也是正則表示式。通常,如果字串p匹配A而另一個字串q匹配B,則字串pq將匹配AB。除非A或B包含低優先順序操作 ,A和B之間有邊界條件; 或編號組參考,否則這是成立的。因此,複雜的表示式可以很容易地從更簡單的原始表示式構建,就像這裡描述的那樣。有關正則表示式的理論和實現的詳細資訊,請參閱Friedl一書,或幾乎任何有關編譯器構造的教科書。

下面是正則表示式格式的簡要說明。有關更多資訊和簡單的介紹,請參閱 正則表示式HOWTO

正則表示式可以包含特殊字元和普通字元。大多數普通字元,如A, a或者0是最簡單的正則表示式; 他們只是匹配自己。您可以連線普通字元,因此last匹配字串'last'。(在本節的其餘部分中,我們將編寫RE ,模式通常沒有引號如this special style,要匹配的字串帶引號,如'in single quotes'。)

某些字元,如'|''(',是特殊的。特殊字元或者代表普通字元的類,或者影響它們周圍的正則表示式的解釋方式。

重複限定符(*+?{m,n}等等)不能直接巢狀。這避免了非貪婪修飾符字尾?的模糊性 ,以及其他實現中的其他修飾符。為了對內部重複應用,可以使用括號。例如,表示式(?:a{6})*匹配六個’a’字元的任意倍數。

1.1 特殊字元

.

點。在預設模式下,它匹配除換行符之外的任何字元。如果已指定DOTALL標誌,則匹配包括換行符在內的任何字元。

def re_bs():
    oris = '\nhi,@leng!\n'
    valid = re.compile(r".")
    valid1 = re.compile(r".", re.DOTALL)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
# 輸出結果
['h', 'i', ',', '@', 'l', 'e', 'n', 'g', '!']
['\n', 'h', 'i', ',', '@', 'l', 'e', 'n', 'g', '!', '\n']

我的環境是windows系統,換行符是\n,你的系統換行符可能不一樣。

^

Caret。匹配字串的開頭,並且在多行模式re.MULTILINE下也會在每個換行符後立即匹配。

def re_caret():
    oris = 'temp\nhi,@leng!\n'
    valid = re.compile(r"^.*")
    valid1 = re.compile(r"^.*", re.MULTILINE)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
    valid = re.compile(r"^")
    valid1 = re.compile(r"^", re.MULTILINE)
    print(re.findall(valid, oris))
    print(re.findall(valid1, oris))
# 輸出結果
['temp']
['temp', 'hi,@leng!\r', '']
['']
['', '', '']

$

匹配字串的結尾或在字串末尾的換行符之前,並且在re.MULTILINE模式中也匹配換行符之前。 foo 匹配'foo''foobar',而正則表示式foo$只匹配'foo'。更有趣的是, 通常foo.$'foo1\nfoo2\n'中搜索並匹配到'foo2',但在re.MULTILINE模式中會匹配到'foo1';
搜尋單個$'foo\n'中將找到兩個(空)匹配:一個在換行符之前,一個在字串末尾。

def re_end():
    oris = 'foo1\nfoo2\n'
    valid = re.compile(r"foo.$")
    valid1 = re.compile(r"foo.$", re.MULTILINE)
    print(re.findall(valid,oris))
    print(re.findall(valid1,oris))
    oris2 = 'foo\n\n'
    valid2 = re.compile(r"$")
    valid3 = re.compile(r"$",re.MULTILINE)
    print(re.findall(valid2,oris2))
    print(re.findall(valid3,oris2))
# 輸出結果
['foo2']
['foo1', 'foo2']
['', '']
['', '', '']

譯者注:在不指定re.MULTILINE的情況下,搜尋開始^和結束$的返回空匹配個數不一樣。

*

使得到的RE匹配 前面RE的0或更多次重複,儘可能多的重複。 ab*將匹配'a','ab'或'a',後跟任意數量的'b'

def re_star():
    oris = 'c,a,ab,abb,abbc,1abD'
    valid = re.compile(r"ab*")
    print(re.findall(valid, oris))
# 輸出結果
['a', 'ab', 'abb', 'abb', 'ab']

+

使得到的RE匹配前一個RE的1次或更多次重複。 ab+將匹配’a’後跟任何非零數字的’b’; 它不會只匹配'a'

def re_star():
    oris = 'c,a,ab,abb,abbc,1abD'
    valid = re.compile(r"ab+")
    print(re.findall(valid, oris))
# 輸出結果
['ab', 'abb', 'abb', 'ab']

?

使得到的RE匹配前面RE的0或1次重​​復。 ab?將匹配'a'或'ab'

def re_wen():
    oris = 'c,a,ab,abb,abbc,1abD'
    al = ['a','ab','abb']
    valid = re.compile(r"ab?")
    print(re.findall(valid,oris))
# 輸出結果
['a', 'ab', 'ab', 'ab', 'ab']

*?,+?,??

'*','+'和'?'限定符都是貪婪的 ; 它們匹配儘可能多的文字。有時這種行為是不可取的; 如果RE <.*>匹配'<a> b <c>',它將匹配整個字串,而不僅僅是'<a>'。在限定符之後新增?使其以非貪婪或最小的方式執行匹配; 儘可能少的 字元將匹配。使用RE <.*?>僅匹配'<a>'

def re_greedy():
    oris = "<html>CSS<title>HELLO</title><body>" + \
           "<p class='page'>P1</p><p class='page2'>P2</p></body></html>"

    valid = re.compile(r"<p.*>")
    print(re.search(valid,oris).group())
    valid = re.compile(r"<p.*?>")
    print(re.search(valid, oris).group())
# 輸出結果
<p class='page'>P1</p><p class='page2'>P2</p></body></html>
<p class='page'>

{m}

指定應匹配前一個RE的m個副本; 較少的匹配導致整個RE不匹配。例如,a{6}將匹配正好六個’a’字元,但不匹配五個字元。

def re_m():
    oris = 'aabaaac'
    valid = re.compile(r"a{3}")
    print(re.findall(valid, oris))
# 輸出結果
['aaa']

{m,n}

使得到的RE匹配前一個RE的m到n次重複,嘗試匹配儘可能多的重複。例如, a{3,5}將匹配3到5個’a’字元。省略m指定零的下限,省略n指定無限上限。作為一個例子,a{4,}b將匹配'aaaab'或一千個’a’字元後跟一個’b’,但不是'aaab'。可以不省略逗號,或者將修飾符與先前描述的形式混淆。

def re_mn():
    oris = 'aabaaaacaaaaa'
    valid = re.compile(r"a{3,5}")
    print(re.findall(valid,oris))
    valid = re.compile(r"a{,3}")
    print(re.findall(valid, oris))
    valid = re.compile(r"a{1,}")
    print(re.findall(valid, oris))
# 輸出結果
['aaaa', 'aaaaa']
['aa', '', 'aaa', 'a', '', 'aaa', 'aa', '']
['aa', 'aaaa', 'aaaaa']

{m,n}?

使得到的RE匹配前一個RE的m到n次重複,嘗試匹配儘可能少的重複。這是前一個限定符的非貪婪版本。例如,在6個字元的字串上'aaaaaa'a{3,5}將匹配5個'a'字元,而a{3,5}?只匹配3個字元。

\

要麼轉義特殊字元(允許你匹配像'*''?'等等那樣的字元 ),要麼發出特殊序列的訊號; 下面討論特殊序列。

如果你沒有使用原始字串來表達模式,請記住Python也使用反斜槓作為字串文字中的轉義序列; 如果Python的解析器無法識別轉義序列,則反斜槓和後續字元將包含在結果字串中。但是,如果Python會識別結果序列,則反斜槓應重複兩次。這很複雜且難以理解,因此強烈建議您使用原始字串,除了最簡單的表示式。

def re_slash():
    oris = 'abc*?e'
    valid = re.compile(r"abc*?e")
    print(re.findall(valid, oris))
    valid = re.compile(r"abc\*\?e")
    print(re.findall(valid, oris))
# 輸出結果
[]
['abc*?e']

[]

用於表示一組字元。在一組:

  • 字元可以單獨列出,如[amk]將匹配'a', 'm'或'k'
  • 範圍內的字元可以通過給兩個字元,並通過-把它們連線起來,例如[a-z]將匹配任何小寫ASCII字母, [0-5][0-9]將所有從00到59的兩位數字,·[0-9A-Fa-f]會匹配任何十六進位制數字。如果-被轉義(例如[a\-z])或者如果它被放置為第一個或最後一個字元(例如[-a][a-]),它將匹配文字'-'
  • 特殊字元在內部失去特殊意義。例如, [(+*)]將匹配任何文字字元的'(','+', '*',或')'
  • 在集合中也接受諸如\w\S(在下面定義)的字元類,儘管它們匹配的字元取決於 ASCII或LOCALE模式是否有效。
  • 不在範圍內的字元可以用^排除。如果集合的第一個字元是^,則將匹配該組中不存在的所有字元。例如,[^5]將匹配除了'5'之外的任何字元,並且[^^]將匹配除了'^'之外的任何字元 。 如果^它不是集合中的第一個字元,則沒有特殊含義。
  • 要匹配集合中的']',請在其前面加上反斜槓,或將其放在集合的開頭。例如,無論是[()[\]{}][]()[{}]都將匹配一個括號。
  • 將來可能會新增對 Unicode技術標準#18 中的巢狀集和集合操作的支援。這將改變語法,因此為了促進這種改變,FutureWarning暫時會在模稜兩可的情況下提出。這包括文字集合以'['開始或含有文字字元序列'--''&&''~~',和'||'。為了避免警告,請使用反斜槓來避開它們。
    版本:python 3.7 -> 如果字符集包含將來將在語義上更改的構造,警告FutureWarning。
def re_list():
    oris = 'a-bcd^[+]'
    valid = re.compile(r"[a-d]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[a\-d]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[-ad]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[^-ad]")
    print(re.findall(valid, oris))
    valid = re.compile(r"[-^ad+]")
    print(re.findall(valid, oris))

    valid = re.compile(r"[d\]a]")
    print(re.findall(valid, oris))
    valid = re.compile(r"[]a[d]")
    print(re.findall(valid, oris))
    print("------------------")
	oris = '[--hello]'
    valid = re.compile(r"[[s]")
    print(re.findall(valid, oris))
# 輸出結果
['a', 'b', 'c', 'd']
['a', '-', 'd']
['a', '-', 'd']
['b', 'c', '^', '[', '+', ']']
['a', '-', 'd', '^', '+']
['a', 'd', ']']
['a', 'd', '[', ']']
------------------
xxx/re1.py:126: FutureWarning: Possible nested set at position 1
  valid = re.compile(r"[[s]")
['[']

|

A|B,其中A和B可以是任意RE,建立一個與A或B匹配的正則表示式。通過|這種方式可以分開任意數量的RE 。這也可以在組內使用(見下文)。掃描目標字串時,’|‘從左到右嘗試分隔的RE 。當一個模式完全匹配時,接受該分支。這意味著一旦A匹配,B將不會被進一步測試,即使它會產生更長的整體匹配。換句話說,’|'操作符從不貪心。要匹配文字'|',請使用\|或將其括在字元類中,如[|]

def re_or():
    oris = 'abc'
    valid = re.compile(r'ab|bc')
    valid1 = re.compile(r'ac|bc')
    print(re.search(valid,oris))
    print(re.search(valid1, oris))
# 輸出結果
<re.Match object; span=(0, 2), match='ab'>
<re.Match object; span=(1, 3), match='bc'>

(…)

匹配括號內的正則表示式,並指示組的開始和結束; 可以在執行匹配後檢索組的內容,並且稍後可以在字串中與\number 特殊序列匹配,如下所述。要匹配的文字'('')'使用\(\),或將它們括中括號間:[(][)]

def re_group():
    # 匹配分開的兩個數字
    oris = 'abc45fg'
    valid = re.compile(r'(\d)(\d)')
    print(re.search(valid, oris).groups())
    print(re.search(valid, oris).group(1))
    print(re.search(valid, oris).group(2))
    # 匹配括號
    oris = 'abc(45)fg'
    valid = re.compile(r'[(]\d\d[)]')
    print(re.search(valid, oris).groups())
# 輸出結果
('4', '5')
4
5
()

(?..)

這是一種擴充套件符號('?'跟隨一個'('除此之外沒有意義)。'?'之後的第一個字元確定構造的含義和進一步語法。擴充套件通常不會建立新組; (?P<name>...)是這條規則的唯一例外。以下是當前支援的擴充套件。

(?aiLmsux)

(集合'a','i','L','m', 's','u','x'中的一個或多個字母。)該組匹配空字串; 這些字母為整個常規設定相應的標誌: re.A ( ASCII-only matching), re.I(ignore case),re.L(locale dependent), re.M(multi-line),re.S(dot match all), re.U(Unicode匹配)和re.X(verbose)表達。(標誌在模組內容中描述。)如果您希望將標誌包含在正則表示式的一部分中,而不是將標誌引數傳遞給 re.compile()函式,這將非常有用。應首先在表示式字串中使用標誌。

(?:…)

常規括號內的非捕獲版本。匹配括號內的正則表示式,但在執行匹配或稍後在模式中引用後,無法檢索組匹配的子字串 。


譯者例項:

def re_fen_not_catch():
    oris = 'abcaBcdeabc'
    # 情況1
    m= re.search(r'(abc)(.*?)(\1)', oris)
    print("正常括號分組1:"+m.group(1))
    print("正常括號分組2:"+m.group(2))
	# 情況2
    valid = re.compile(r'(?:abc)(.*)(\1)')
    m = re.search(valid,oris)
    print("非捕獲括號分組1:"+m.group(1))
    print("非捕獲括號分組2:"+m.group(2))
    
    # 情況3
    oris = 'abcabcaBcdeabc'
    m = re.search(r'(?:abc)(abc)(.*?)(\1)', oris)
    print("非捕獲括號分組3:" + m.group(1))
    print("非捕獲括號分組4:" + m.group(2))
# 輸出結果
正常括號分組1:abc
正常括號分組2:aBcde
非捕獲括號分組1:
非捕獲括號分組2:
非捕獲括號分組3:abc
非捕獲括號分組4:aBcde

分析:
(1)從上面的例子對比來看(...)(?:...)的區別就比較明顯了,後者確實匹配正則表示式,但是不會加入分組中。
(2)情況1中,(abc)匹配到開頭的'abc',將其加入分組1,使得後面可以使用分組序號(\1)group(1),中間的內容成為分組2。
(3)情況2中,(?:abc)匹配到開頭的'abc',但是將其加入分組1,導致\1group(1)不存在,從而使得分組2也沒有了。
(4)情況3中,對源字串增加了'abc',此時雖然第一個'abc'沒加入分組,但是第二個正常加入分組,所以就有了\1,剩餘同 情況1。


(?aiLmsux-imsx:…)

(集合'a','i','L','m', 's','u','x'中的一個或多個字母,可選地接著-隨後是集合'i','m','s','x'中的一個或多個字