python的re模塊--細說正則表達式
可能是東半球最詳細最全面的re教程,翻譯自官方文檔,因為官方文檔寫的是真的好,所以,盡量保持原本的東西,做最少的改動,保持原汁原味!干貨十足。如果能耐著性子看完,一定會感覺編程能力上一個新的檔次!
大家有空也可以到我的個人博客上看看
前言
什么是正則表達式,regular expression在英語中是有規則的表達式,也就是說,該表達式只是一條的規則,而正則表達式引擎能夠根據這條規則,幫你在字符串中尋找所有符合規則的部分,比如,我有一條字符串"hello world 123"
而規則,可以很具體,比如hello
那么引擎會幫你把hello
從字符串中找出來,規則也可以比較抽象,比如\d
這個表示數字,也就是引擎會幫你把字符串中的數字尋找出來,根據正則表達式的理論,我們可以把規則串聯起來,變成一條更復雜的規則。想要從一條字符串中找到對你來說有意義的部分,你的任務可能不僅僅是從字符串中提取數字這么簡單,因此需要設計非常復雜的規則,而本教程則會告訴你,如何去制定非常那些非常復雜的規則。事實上,制定好規則之后,引擎不僅能夠根據規則進行查找,還可以進行分割,替換等復雜的操作,能讓你隨心所欲得處理字符串。本教程默認你懂得一些python的簡單語句尤其是對str處理時的語句。
python中的re模塊提供無比強大的正則表達式功能。如果需要更加逆天的正則表達式功能,第三方regex模塊具有與標準庫re模塊兼容的API,但提供了額外的功能和更全面的Unicode支持。不過python自帶的re模塊已經足夠牛逼了,只要你能夠掌握下面的內容。
學習正則表達式的意義
為什么要學習正則表達式?不僅python,絕大多數語言的正則表達式的設計理念都是差不多的,你在這里學熟練了,那么對于其他語言的正則表達式也會融會貫通。學會正則表達式,不僅能提升編程能力,還能讓你的工作和生活更加方便。比如,有人在處理文章的時候,想把文章的數字標號"第1章"變成漢字數字編號"第一章",如果章節非常多,手動修改是非常痛苦的,而word自帶的替換功能已經無能為力了,那么可以考慮使用一個功能強大的編輯器比如sublime然后通過正則表達式進行替換。類似的文本處理在工作和生活中經常出現,掌握正則表達式絕對會讓你能夠更加輕松應對這些場景。此外,學會正則表達式能夠做的東西非常多,也非常酷,比如本教程最后講的分詞器,就是編寫編譯器的關鍵步驟,沒錯,學完正則表達式,是開發一個屬于你自己的語言的關鍵步驟,說不定你的語言能夠和python一樣廣受歡迎。
關于\
正則表達式使用反斜線字符 \
作為Escape符,讓特殊字符失去含義。這與Python在字符串的使用相沖突,所以,如果我們需要匹配反斜杠,我們需要輸入的正則表達式應該是\\
也就是我們要輸入兩個反斜杠,這樣一來,要告訴python的字符串,這里有兩個反斜杠,那么,我們的python字符串里面就需要有四個反斜杠\\\\
。
!!!如果你覺得很難理解,沒關系,只要記得下面的解決方案就可以了。
解決方案是將Python的原始字符串表示法用于正則表達式模式;在以 'r' 為前綴的字符串文字中不以任何特殊方式處理反斜杠。所以 r"\n"
是包含 \
和 'n' 的兩個字符的字符串,而 "\n" 是包含換行符的單字符字符串。re讀取了 \
和 n
連續兩個字符,他就知道,這種表達式是要匹配一個換行符。總而言之,使用的時候一定要使用r'str'這樣的模式,你在查找資料的時候看到也經常是這種情況。
!!!以上內容看不懂沒關系,只要記住,python的正則表達式都是 r"str" 這樣的形式就可以了!
正則表達式規則
規則是相通的,除了python,其他語言的正則表達式規則基本上都是一樣的,只是語言的實現不一樣,因此,我們先看看什么是正則表達式規則。
正則表達式(RE)實際上就是一條規則,能讓字符串的某些部分匹配上,或者說該部分符合這條規則。有時候規則比較具體,如找出字符串中所有的hello
,那么就字符串中一定要是要有'hello'才能匹配,其他的'Hello',或者'HELLO',再或者有錯別字的'gello'都是不符規則的。有時候呢,可以放寬一些規則,比如允許第一個字母是錯別字,只要是小寫字母就好了,那么就可以這樣寫規則[a-z]ello
,其中 [a-z]
表示a到z中的任意一個。我們可以看到,在規則[a-z]ello
中既有ello
這樣的普通字符,又有[a-z]
這樣的特殊規則,組成了一條完整的規則,我們也能看出,只有普通字符那么規則是非常具體嚴格的,加上[a-z]
這樣的特殊符號之后規則變得更加靈活。所以,特殊符號才是正則表達式強大所在,我們可以使用不同的特殊符號進而構造更加復雜的規則。
下面就看看python的正則表達式有哪些特殊符號。(其他的語言中的特殊符號也都大同小異)
值得說明一下的是,下面的規則有些非常復雜,很多人可能這輩子都用不上,所以,看到你覺得復雜或者看不懂的規則,你可以跳過去,沒有任何影響,或許你在看完全部的內容之后再回過來看這些特殊的符號,特殊的規則,會有新的領悟,所以,學習正則表達式不可能一下就能學會的,一定要多看幾遍,多練習,才能融會貫通,因為學習最主要的還是要運用。
特殊符號(又稱特殊字符)如下:
.
在默認模式下,它匹配除換行符以外的任何字符。(如果已經指定了DOTALL標志,則它匹配包括換行符的任何字符。至于如何設定這個,我們之后再講)
因此我們的設定規則 .ello
的時候,符合這條規則的字符串有'hello','8ello','kello'...非常多,不管開頭是什么字符,只要后面是'ello'就符合規則。
^
匹配字符串的開頭,并且在MULTILINE模式下,也會在每個換行符后立即匹配。
我們設定規則^hello
那么字符串 'hello world'里面的'hello'能夠符合規則,而'world hello'里面的'hello'不符合規則,因為它不在字符串的開頭。
由于在Multiline模式下,也匹配每個換行符后,所以,在開啟了multiline模式下,
'world\nhello'中的'hello'也符合規則。
$
匹配字符串的結尾或緊挨在字符串末尾的換行符之前,并且在MULTILINE模式下也匹配換行符之前的匹配。
foo
匹配 'foo' 和 'foobar' ,而正則表達式 foo$
只匹配 'foo'或者'hello foo'。 更有趣的是,在 "foo1\nfoo2\n" 中搜索 foo.$
通常與 'foo2' 匹配,而在MULTILINE模式下得到的是'foo1'。在'foo\n'中搜索單個 $
將會找到兩個(空)匹配:一個位于換行符之前,另一個位于字符串末尾。
*
對
*
前面的RE匹配0次或多次,并盡可能多的重復。
我們設定規則 ab*
,意思是對b
可以重復匹配。 將匹配'a','ab'或'a'后跟任意數量的'b'。而且*
默認的是貪婪模式,也就是說對于字符串'abbb'規則認為整個字符串符合規則,而不是子字符串符合規則。將來我們要求返回符合規則的部分時,將會返回整個字符串abbb而不僅僅是ab或者a。
+
對于'+'前面的RE進行一個或多個重復。
ab+
將與 'ab' 匹配,后面還可以跟任意個'b' ,但這條規則不會匹配 'a' 。值得注意的是,這條規則也是貪婪的。
?
對
?
前面的RE進行0或1個重復。
ab?
將匹配 'a' 或 'ab'。這條規則也是貪婪的。
*?, +?, ??
*
,+
和?
限定符都是貪婪的; 它們匹配盡可能多的文本。 有時候這種行為是不希望的; 如果RE<.*>
與 '<a>b<c>' 匹配,它將匹配整個字符串,而不僅僅是 '<a>' 。 添加? 在限定符之后以非貪婪或最小方式進行匹配; 盡可能少的字符將被匹配。 使用RE<.*?>
將只匹配 '<a>'。
值得一提的是,我們看到 a*?
我們應當想到這是一條關于*
的規則,而不是關于?
的規則,后者只是為前者服務的。
{m}
指定應該匹配
{m}
前一個RE的正好m個副本; 更少的匹配導致整個RE不匹配。 例如,a{6}
將完全匹配六個 'a' 字符,但不是五個。
如果多于6次,那么多出的部分被忽略。
{m,n}
使得到的RE匹配前面RE的m到n次重復,試圖盡可能多地匹配重復。 例如,
a{3,5}
將匹配3到5個 'a' 字符。省略m則指定零作為下限,省略n指定無限上限。 例如,a{4,}b
會匹配 'aaaab' 或一千個 'a' 字符,后跟一個 'b',但不匹配 'aaab'。 逗號不能省略,否則修飾符會與之前描述的表單混淆。
這條規則也是貪婪的,它會盡可能到達重復n次
{m,n}?
使得到的RE從m到n重復前面的RE,嘗試匹配盡可能少的重復。 這是以前限定符的非貪婪版本。 例如,在6個字符的字符串'aaaaaa'上,
a{3,5}
將匹配5個 'a'字符,而a{3,5}?
只會匹配3個字符 'a'。
\
要么特殊字符失去意義,要么標志一個特殊序列; 之后會討論特殊序列。
首先,你在python中寫規則的時候一定要用raw字符串,也就是r'...',否則就會被''的特殊性給繞暈。其次要記住,這個符號叫Escape符號,使得特殊符號逃離特殊功能,比如,我們要匹配一個'.',那么我們寫規則的時候就需要這樣寫 r'\.'
[]
用于指示一組(或者說一個集合)字符。在一組中:
- 字符可以單獨列出,例如
[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'以外的任何字符,并且[^^]
將匹配除 '^' 以外的任何字符。 '^' 如果它不是集合中的第一個字符,它沒有特別的意義。 - 在一個集合內匹配']',在它之前加一個反斜杠,或者將它放在集合的開頭。例如, [()[]{}]和[{}]都將與括號匹配。
|
A|B,其中A和B可以是任意RE,創建一個匹配A或B的正則表達式。任意數量的RE可以用'|'分隔。通過這種方式,這可以在組內使用(見下文)。當目標字符串被掃描時,由'|'分隔的RE 從左到右嘗試。當一個模式完全匹配時,該分支被接受。 這意味著一旦A匹配,B將不會被進一步測試,即使它會產生更長的整體匹配。 換句話說, '|'操作從不貪婪。要匹配'|',請使用
\|
,或將其放在字符類中,如[|]
中所示
\d
對于Unicode(str)模式:
- 匹配任何Unicode十進制數字。這包括[0-9]以及許多其他數字字符。 如果使用ASCII標志,則只匹配[0-9]。
對于8位(字節)模式:
- 匹配任何十進制數字; 這相當于[0-9]。
\D
匹配任何不是十進制數字的字符。 這與\d相反。 如果使用ASCII標志,則相當于[^ 0-9]。
\s
對于Unicode(str)模式:
- 匹配Unicode空白字符(包括[\t \n \r \f \v]以及許多其他字符,例如許多語言中的排版規則強制的非空白空格)。 如果使用ASCII標志,則只匹配[\t \n \r \f \v]。
對于8位(字節)模式:
- 在ASCII字符集中匹配被認為是空白的字符; 這相當于[\t \n \r \f \v]。
\S
匹配任何不是空白字符的字符。 這與\s相反。 如果使用ASCII標志,則這等價于[^ \t \n \r \f \v]。
\w
對于Unicode(str)模式:
- 匹配Unicode字符; 這包括大多數可以是任何語言的單詞的一部分的字符,以及數字和下劃線。 如果使用ASCII標志,則只匹配[a-zA-Z0-9_]。
對于8位(字節)模式:
- 匹配ASCII字符集中被認為是字母數字的字符; 這相當于[a-zA-Z0-9_]。 如果使用LOCALE標志,則匹配在當前語言環境和下劃線中被認為是字母數字的字符。
\W
匹配任何不是單詞字符的字符。 這與\ w相反。 如果使用ASCII標志,則變成等效于[^ a-zA-Z0-9_]。 如果使用LOCALE標志,則匹配在當前語言環境和下劃線中被認為是字母數字的字符。
\Z
只匹配字符串的末尾。只能放在正則表達式末尾
進階規則
(...)
匹配括號內的任何正則表達式,并指示組的開始和結束; 在匹配完成后可以檢索組的內容,并且可以在后面的字符串中使用\number特殊序列進行匹配,之后有詳細描述。 要匹配'('或')',請使用
\(
或\)
,或將它們放在字符類中:[(],[]]
。 舉個例子我有個字符串 'asdfa020-12345678bsaefga' 可以看到我們的電話號碼被一群字母圍住了,怎么把電話號碼拿出來,順便把區號和真正的號碼都分開呢? 可以用這樣的規則(\d{4})-(\d)* 這樣符合結果字符串就是 '020-12345678' 但是,返回的結果對象并不僅僅是一個字符串,他還有.group,其中 group(1) = '020' group(2)='12345678'
\number
匹配相同編號的組的內容。 例如
(.+) \1
這個式子等價于(.+) (.+)
和'the the' 或 '55 55'匹配,但不匹配'thethe'(注意組之后的空格)。 該特殊序列只能用于匹配前99個組中的一個。 如果數字的第一個數字是0或數字是3個八進制數字長度,則不會將其解釋為組匹配,而是將其解釋為具有八進制數值的字符(這句話初學者可以略過)。
這個規則非常棒,能幫你省不少事情,比如一個字符串里面有兩個郵件地址,你想把它們都找出來,你寫好了一個檢測郵件地址規則,剩下那個你不想寫了,你可以引用一下就可以了。
溫馨提示,下面的特殊符號可能比較難,需要耐心去體會,或者干脆略過。
(?P<name>...)
與常規圓括號類似,但可以通過符號組名稱來訪問與該組匹配的子字符串。 組名稱必須是有效的Python標識符,并且每個組名稱只能在正則表達式中定義一次。
相當于給我們之前提到的組(...)
添加了一個名字而已,添加名字的好處是可以根據名字來引用這個組,而不是靠編號。
相當于給我們之前提到的組(...)
添加了一個名字而已,添加名字的好處是可以根據名字來引用這個組,而不是靠編號。舉個之前的例子,有字符串 'asdfa020-12345678bsaefga' ,我想取出號碼,可以這樣寫
(?P<quhao>\d{4})-(?P<haoma>\d*)
這樣返回的結果字符串就是020-12345678,但是返回的對象還有group屬性,而且 group['quhao'] = 020 group['haoma'] = 12345678
命名組可以在三種情況下被引用。 比如我們想在句子中找到引號,這樣我們就能找到文章中引用的內容,所以,我們可以這樣寫
(P<quote>['"])
這樣就能找到一個單引號或者雙引號,但是一般來說,我們需要找到一對單引號或者雙引號,這時我們不必在寫一個單引號或雙引號的正則表達式,只要引用一下之前的就好了,如(?P<quote>['"]).*?(?P=quote)
后面一個實際上就是對于前面的引用,這時再正則表達式中引用的情況,也就是下表中第一種情況。實際上不僅規則中很有可能會引用前面寫好的組,還有其他的兩種情況需要引用,請見表
引用'quote' | 引用的方法 |
---|---|
在正則表達式中 |
(?P=quote) \1
|
在匹配的結果中 |
m.group('quote') m.end('queto')
|
在re.sub中的repl里 |
\g<quote> \g<1> \1
|
(?P=name)
對指定組的反斜線引用; 它匹配與早先的組命名相匹配的任何文本。見上一條的說明
(?...)
這是一個擴展符號。'?'后面的第一個字符決定了結構的含義和進一步語法。擴展通常不會創建一個新的組。
(?P<name> ...)
是這個規則的唯一例外,以下是當前支持的擴展。
(?aiLmsux)
(?號后面跟著'a','i','L','m','s','u','x'的一個或多個字母)。這些字母設置相應的標志:re.A(僅ASCII匹配),re.I(忽略大小寫),re.L(依賴于語言環境),re.M(多行),re.S(點匹配全部) ,re.U(Unicode匹配)和re.X(詳細),用于整個正則表達式。這些標志的作用在模塊內容中有具體的描述。事實上,這些標志可以通過flag參數傳給正則表達式,也可以像這樣直接寫進正則表達式里面。
(?:...)
常規圓括號的非捕獲版本。 匹配括號內的任何正則表達式,但匹配的子字符串在執行匹配或稍后引用模式后無法檢索。
(?aiLmsux-imsx:...)
(來自'a','i','L','m','s','u','x'的零個或多個字母,可選地后面跟著' - ',后面跟著一個或多個來自'','m','s','x')。字母設置或刪除相應的標志:re.A(僅ASCII匹配),re.I(忽略大小寫),re.L(依賴于語言環境),re.M(多行),re.S(點全部匹配),re.U(Unicode匹配)和re.X(冗長),用于表達部分。 注意,這里上面提到的(?aiLmsux)的區別是,這里有一個冒號,后面跟正則表達式,因此這里設置的標志僅適用于窄內聯組,并且原始匹配模式在組外部恢復。在設置標志的時候(?aiLmsux-imsx:...)等價于(?aLu:...),也就是沒必要刻意先在左邊寫進去后邊再跟隨'-'號刪掉,但是,有時候'-'也是很有用的,比如之前設置了(?i)說明對所有的正則表達式都忽略大小寫,但是你對某一個部分需要強調大小寫,比如,你想要找superMAN對前面的super的大小寫無所謂,但要求MAN一定是大寫,可以這樣寫
(?i)super(?-i:MAN)
這樣 sUpeRMAN能匹配,而superMan則不能匹配
(?#...)
一條評論; 圓括號的內容被簡單地忽略。
(?=...)
如果...匹配next,但不消耗任何字符串。 這被稱為前瞻斷言。 例如,
Isaac(?=Asimov)
只有跟隨著'Asimov'才會匹配'Isaac'。但返回的結果依然還是Isaac,所謂不消耗字符串,意思是,后面的Asimov依然可以被繼續匹配,如
Isaac(?=[A])AsimovAlab
匹配 'IsaacAsimovAlab'
而Isaac(?=[Asimov])G
則會無法匹配任何字符串,因為根據(?=...)要求,Issac后面必須是Asimov,而它后面又要跟著一個G,所以,互相矛盾。
(?!...)
如果...不匹配。 這是一個負面的前瞻斷言。 例如,Isaac(?!Asimov)只有在沒有跟隨'Asimov'時才會匹配'Isaac'。
(?<=...)
匹配如果字符串中的當前位置在...之前匹配...,并以當前位置結束。 這被稱后行斷言。
(?<= abc)def
會在'abcdef' 中找到一個匹配項,因為lookbehind會回看3個字符并檢查包含的模式是否匹配。 包含的模式只能匹配一些固定長度的字符串,這意味著abc
或a|b
是允許的,但a*
和a{3,4}
不是。 請注意,像這種向前看的搜索字符串模式在匹配開頭的時候會出現問題;因此 必須使用search()函數而不是match()函數:仔細想想為什么?
>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'
本示例在連字符后面查找單詞:
>>> m = re.search(r'(<=-)\w+', 'spam-egg')
>>> m.group(0)
'egg'
(?<!...)
上一例子的不匹配情況
(?(id/name)yes-pattern|no-pattern)
如果存在給定id或名稱的組,則嘗試與yes-模式匹配,如果不存在,則使用無模式。 無圖案是可選的,可以省略。 例如,正則表達式
(<)?(\w+@\w+(?:\.\w +)+)(?(1)>|$)
是一個電子郵件匹配模式,它將匹配'<user@host.com >'以及'user@host.com',但不匹配'<user@host.com'和'user@host.com>'。為什么,就是因為后面有一個(?(1)>|$)
我們先看看其中的(1)
表示什么,表示正則表達式最前面的那個括號,也就是郵件地址匹配正則表達式中的(<)
如果這個括號匹配到了內容,也就是檢查看到了郵件地址以'<'字符開始,那么,我們對于該郵件地址末尾字符的檢測就是>
,如果(<)
檢測失敗,也就是郵件地址不以'<'開頭,那么我們對于該郵件地址末尾字符的檢測就是'$'
\A
只匹配字符串的開頭。只能放在正則表達式開頭
\b
匹配空字符串,但僅限于單詞的開頭或結尾。一個單詞被定義為一個單詞字符序列。 請注意,在形式上,
\b
被定義為\w
和\W
字符之間的界限,或\w
和字符串的開始/結尾之間的界限。這意味著r'\bfoo\b'
匹配 'foo' , 'foo.' , '(foo)' , 'bar foo baz' ,但不匹配'foobar'或'foo3'。
默認情況下,Unicode字母數字是Unicode模式中使用的字母數字,但可以通過使用ASCII標志來更改。如果使用LOCALE標志,字邊界由當前的區域設置確定。在字符范圍內,\ b代表退格字符,以便與Python中的字符串文字兼容。
\B
匹配空字符串,但僅限于它不在單詞的開頭或結尾。 這意味著
r'py\B'
匹配'python','py3','py2',但不匹配'py','py。'或'py!'。\B
與\b
相反,因此Unicode模式中的單詞字符(\w)是Unicode字母數字加下劃線。 如果使用LOCALE標志,單詞的定義由當前的區域設置確定。
正則表達式對象
我們寫好一條規則,如r'.ello',目前在python里面只是一個字符串,我們要讓這個規則字符串變成正則表達式,需要將這個字符串編譯一下,成為表達式對象,這個對象又稱pattern,即樣式。
re.compile(pattern, flags=0)
通過上面的模塊函數,根據我們寫的規則字符串,生成一個正則表達式對象。
序列
# regex = r'你的規則'
pattern = re.compile(regex) #這里 regex只是規則字符串,pattern才是正則表達式對象
result = pattern.match(string)
等價于
result = re.match(regex, string)
使用re.compile()生成的正則表達式對象可以重用,更高效。因此,推薦使用第一種用法,第二種方法明顯是在背后偷偷編譯了個 pattern,只是為了方便,快捷地使用而已。
正則表達式對象支持以下方法
pattern.search(string[, pos[, endpos]])
掃描字符串查找第一個能匹配上pattern的部分,并返回相應的匹配對象。 如果字符串中沒有位置與模式匹配,則返回None; 請注意,這與在字符串中的某處找到零長度匹配不同。如果你本身要匹配的就是一個空字符,如上述規則中的r'\b',那么返回一個""表示的是找到了對應的r'\b',也就是找到了空字符,而返回None找不到任何匹配的空字符。
可選的第二個參數pos在搜索要開始的字符串中給出一個索引,它默認為0,從指定的pos開始搜索,也就是說避開字符串開始的部分。pattern.search("hello world") 表示在 hello world 中查找
pattern.search("hello world",2) 表示在 llo world 中查找
可選參數endpos表示搜索字符串的截止位置,也就是說,你想避開字符串結尾的部分。只有從pos到endpos - 1的字符才會被搜索到。如果endpos小于pos,則不會找到匹配,否則pattern.search(string,0,50)等同于pattern.search(string[:50],0)。
>>> pattern = re.compile("d")
>>> pattern.search("dog") # 能匹配
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1) # 不能匹配
我們可以看到,我們首先,寫好一個規則d
,這是一個具體的規則,沒有用到任何特殊符號,接著,我們把這條規則編譯成pattern,這是一個正則表達式對象,然后使用pattern的search函數,在字符串"dog"中搜索。匹配的結果返回一個Match對象,之后會詳細講解這個Match對象。
Pattern.match(string[, pos[, endpos]])
如果字符串開頭的零個或多個字符與此正則表達式匹配,則返回相應的匹配對象。 如果字符串與模式不匹配,則返回None; 請注意,這與零長度匹配不同。
可選的pos和endpos參數與search()方法具有相同的含義。
>>> pattern = re.compile(r"o")
>>> pattern.match("dog") # 不匹配,因為 "o" 不是 "dog"的最前面.
>>> pattern.match("dog", 1) # 能匹配 "o" 是 "dog" 的第二個字母.
<re.Match object; span=(1, 2), match='o'>;
這個函數和search差不多,但是,規定一定要從起始位置就得匹配上,否則就不算匹配成功。比如,我們在"dog"中搜索o
但是,由于開始的位置不是o
所以匹配失敗。
如果您想在字符串中的任何位置找到匹配項,請改用search()(另請參閱search()與match())。
pattern.fullmatch(string[, pos[, endpos]])
如果整個字符串匹配此正則表達式,則返回相應的匹配對象。 如果字符串與模式不匹配,則返回None; 請注意,這與零長度匹配不同。
可選的pos和endpos參數與search()方法具有相同的含義。
>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog") # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre") # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3) # Matches within given limits.
<re.Match object; span=(1, 3), match='og'>
這個函數規定整個字符串要跟pattern匹配上,才算匹配成功。
pattern.split(string, maxsplit=0)
平時,如果我們想拆分一個字符串,python內置的split函數需要寫入固定的分拆字符,比如下面的字符串"abc1efg1rgh" 我們可以以'1'字符來分拆這個字符串,但是萬一,分割字符串的不只是'1'而是其他數字怎么辦?如"abc1efg2hij3klm"python自帶的split就束手無策了。這時候就需要用到正則表達式的拆分了,你會發現解決這個問題是分分鐘的事。
>>> pattern = re.compile(r'[0-9]')
>>> pattern.split("abc1efg2hij3klm")
['abc', 'efg', 'hij', 'klm']
這個函數根據表達式規則分拆字符串,返回一個list。(溫馨提示,后面這點內容可以跳過)如果在表達式中使用捕獲括號,則表達式中所有組的文本也會作為結果列表的一部分返回。 如果maxsplit不為零,則最多發生maxsplit分割,并且字符串的其余部分作為列表的最后一個元素返回。
>>> pattern = re.compile(r'\W+')
>>> pattern.split( 'Words,words,words.')
['Words', 'words', 'words', '']
>>> pattern = re.compile(r'(\W+)')
>>> pattern.split( 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> pattern = re.compile(r'\W+')
>>> pattern.split('Words, words, words.', 1)
['Words', 'words, words.']
>>> pattern = re.compile('[a-f]+',flags=re.IGNORECASE)
>>> pattern.split( '0a3B9' )
['0', '3', '9']
如果分隔符中有捕獲組,并且它在字符串的起始處匹配,則結果將以空字符串開頭。 字符串的結尾也是一樣:
>>> pattern = re.compile(r'(\W+)')
>>> pattern.split( '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']
這樣,分隔符組件總是在結果列表中的相同索引處找到。
pattern.findall(string[, pos[, endpos]])
返回字符串中模式的所有非重疊匹配項,作為字符串列表。 字符串從左到右掃描,匹配按照找到的順序返回。 如果模式中存在一個或多個組,返回組列表; 如果模式有多個組,這將是一個元組列表。 結果中包含空匹配項。
比如,我們想要查找一個字符串里面全部的數字
>>> pattern = re.compile(r'\d+')
>>> pattern.findall("a12b56c54d89")
['12', '56', '54', '89']
如果有分組,比如,我們需要把查找的數字的末尾作為分組提取出來,那么返回的就是數字的最后一位
>>> pattern = re.compile(r'\d*([0-9])')
>>> pattern.findall("a124b567c54d892")
['4', '7', '4', '2']
r'\d*([0-9])' 這條規則本來匹配的是下面的123,567,54,892但是這里由于有了后面的括號,所以只返回括號里面的
如果有多個分組,則返回這些分組的tuple,比如,要返回數字的個位數和十位數
>>> pattern = re.compile(r'\d*([0-9])([0-9])')
>>> pattern.findall("a124b567c54d892")
[('2', '4'), ('6', '7'), ('5', '4'), ('9', '2')]
pattern.finditer(string[, pos[, endpos]])
返回一個迭代器,產生字符串中RE模式的所有非重疊匹配的匹配對象。 字符串從左到右掃描,匹配按照找到的順序返回。
和上面唯一的區別是返回的是一個iter而不是一個list,如果你對Python熟悉的話應該了解這兩者的區別,如果不熟悉的話,建議使用上一種方法就好了,不要管這個。
接受可選的pos和endpos參數,這些參數限制搜索區域,如search()。
pattern.sub(repl, string, count=0)
python自帶replace函數的加強版。
先通過正則表達式規則找到string中符合規則的部分,然后替換成repl
如果未找到能匹配規則的部分,則字符串將保持不變。 repl可以是一個字符串或一個函數; 如果它是一個字符串,則處理其中的任何反斜杠轉義。 也就是\n被轉換為單個換行符,\r被轉換為回車符,等等。 像 & 一樣的未知轉義單獨保留。引用(例如\6)被替換為模式中由組6匹配的子字符串。 例如: 我們把分割字母的數字換成'000'
>>> pattern = re.compile(r'\d+')
>>> pattern.sub('000', '12abc34de56fg89')
'000abc000de000fg000'
repl可以引用分組,因此,假如我們要把分割字母的數字換成他們的個位數,可以這樣.
注意這里repl需要用raw字符串,否則re模塊無法識別'\1',還是那個''的問題。所以,能用raw盡量用raw
>>> pattern = re.compile(r'\d*(\d)')
>>> pattern.sub(r'\1','12abc34de56fg89') # 這里的r'\1'對應的就是上面 r'\d*(\d)'中的(\d)
'2abc4de6fg9'
如果repl是一個函數,它會實現更加復雜的替換,比如我們需要把分割字母的數字翻倍。也就是,我們我們每個匹配到的數字,我們都要通過函數處理一下,以返回值作為repl 例如:
>>> def func(n):
... return str(int(n.group())*2)
...
>>> pattern = re.compile(r'\d+')
>>> pattern = re.sub(func,'12abc34de56fg89')
'24abc68de112fg178'
注意到,傳給func的是一個個匹配對象(后面會講到),該對象包裝了真正匹配到的數字,如'12','34','56','89',所以使用了一個 n.group()來提取數字。匹配對象的強大之處還在于我們不僅可以提取完整的匹配數字如'12',我們還可以提取分組,比如,我們在pattern中設定了個位數字作為組,那么就可以提取出來,如下面的例子,把找到的數字替換成該數字的個位數的兩倍
>>> def func(n):
... return str(int(n.group(1))*2)
# 對比上面n.group()其實是n.group(0)表示匹配的字符串,也就是整個r'\d*(\d)' 能匹配到的字符串
# n.group(1) 對應的就是 r'\d*(\d)' 中的 (\d)匹配到的東西
...
>>> pattern = re.compile(r'\d*(\d)')
>>> pattern.sub(func,'12abc34de56fg89')
'4abc8de12fg18'
關于匹配對象,我們在后面還會有詳細的講解。
pattern.subn(repl, string, count=0)
執行與sub()相同的操作,但返回一個元組(new_string,number_of_subs_made),也就是不僅返回一個和上面函數一樣的字符串,還多返回了一個數字,代表了總共替換的次數,像上面的例子
>>> pattern.subn(func,'12abc34de56fg89')
('4abc8de12fg18', 4)
正則表達式對象的屬性:
pattern.flags
正則表達式匹配標志。在re.compile()函數中設定,比如要忽略大小寫
>>> pattern = re.compile(r'd',flags=re.IGNORECASE)
>>> pattern.findall('DOG')
['D']
如果要設置多個flags,可以用 |
隔開
>>> pattern = re.compile(r'd',flags=re.IGNORECASE | re.MULTILINE)
當然,設置flags也可以通過內聯的方式
```python
>>> pattern = re.compile(r'(?im)d')
>>> pattern = re.compile(r'd',flags=re.IGNORECASE | re.MULTILINE)
上面兩者是等價的。
pattern.groups
查看模式中的捕獲組數量。也就是你在規則中使用捕獲括號的數量
比如 p = re.compile(r'hello(\d)') 那么這時 p.groups 就是 1
pattern.groupindex
如果你在規則設定的時候使用了(?P<name>)
,那么這個變量可以返回該特殊符號使用的情況。
將(?P<id>)
定義的任何符號組名稱映射到組編號的字典。 如果模式中沒有使用符號組,則字典為空。
pattern.pattern
模式對象編譯的模式字符串。就是那個寫下的規則。
>>> p = re.compile(r'(?im)d')
>>> p.pattern
'(?im)d'
re模塊自帶的函數
我們上面學習了使用正則表達式的兩個步驟,首先編譯出正則表達式對象pattern,然后,調用pattern的search,findall,match等函數進行匹配等操作,實際上re直接提供了search,findall,match等快捷操作,允許我們直接操作,而不需先編譯pattern,而是直接把規則寫在操作函數之中。這樣的操作比上面提到的方法減少了一行,有了更好地便捷性,但是也犧牲了復用性等功能。
re.match(pattern, string, flags=0)
下面的兩個匹配操作是等價
>>> pattern = re.compile(r"o")
>>> pattern.match("dog") # No match as "o" is not at the start of "dog".
>>> re.match(r"o","dog") # No match as "o" is not at the start of "dog".
事實上,第二種直接操作在背后也是先用第一個參數編譯成正則表達式對象然后在讓這個對象調用match方法。其他的search,findall,split等操作也是一樣的。
re.search(pattern, string, flags=0)
參考pattern.search
re.fullmatch(pattern, string, flags=0)
參考pattern.full
re.split(pattern, string, maxsplit=0, flags=0)
參考pattern.split
re.findall(pattern, string, flags=0)
參考pattern.findall
re.finditer(pattern, string, flags=0)
參考pattern.finditer
re.sub(pattern, repl, string, count=0, flags=0)
參考pattern.sub
re.subn(pattern, repl, string, count=0, flags=0)
執行與sub()相同的操作,但返回一個元組(new_string,number_of_subs_made)。
re.escape(pattern)
讓pattern中的特殊字符失去意義。 如果您想匹配任何可能具有正則表達式元字符的文字字符串,這非常有用。 例如:
>>> print(re.escape('python.exe'))
python\.exe
>>> p = re.compile(re.escape(r'.')) # 等價于 p = re.compile(r'\.')
>>> p.search("abc")
# 不能匹配
>>> p.search(".")
<_sre.SRE_Match object at 0x05370D08>
再來一個例子
>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*
re模塊自帶的參數
re.A == re.ASCII
使\w,\W,\b,\B,\d,\D,\s和\S執行僅ASCII匹配而不是完全Unicode匹配。 這只對Unicode模式有意義,并且在字節模式中被忽略。 對應于內聯標志(?a)。
請注意,為了向后兼容,re.U標志仍然存在(以及它的同義詞re.UNICODE及其嵌入對象(?u)),但這些在Python 3中是多余的,因為默認情況下匹配是Unicode的Unicode(和Unicode 字節不允許匹配)。
re.I == re.IGNORECASE
執行不區分大小寫的匹配; 像[A-Z]這樣的表達式也將匹配小寫字母。 除非使用re.ASCII標志來禁用非ASCII匹配,否則完全的Unicode匹配(例如ü匹配ü)也是有效的。 除非使用re.LOCALE標志,否則當前語言環境不會更改此標志的效果。 對應于內聯標志(?i)。
請注意,當Unicode模式[a-z]或[A-Z]與IGNORECASE標志組合使用時,它們將與52個ASCII字母和另外4個非ASCII字母匹配:'?'(U + 0130,拉丁語大寫字母I與 (U + 0131,拉丁小字母無點i),'s'(U + 017F,拉丁小寫字母長)和'K'(U + 212A,開爾文符號)。 如果使用ASCII標志,只匹配字母'a'到'z'和'A'到'Z'。
re.L == re.LOCALE
根據當前語言環境,使\ w,\ W,\ b,\ B和不區分大小寫的匹配。 該標志只能用于字節模式。 由于區域設置機制非常不可靠,因此不鼓勵使用此標志,它一次只處理一種"文化",并且僅適用于8位語言環境。 對于Unicode(str)模式,默認情況下,Unicode匹配已在Python 3中啟用,并且它能夠處理不同的語言環境/語言。 對應于內聯標志(?L)。
在版本3.6中更改:re.LOCALE只能與字節模式一起使用,并且與re.ASCII不兼容。
在版本3.7中更改:使用re.LOCALE標志編譯的正則表達式對象在編譯時不再依賴于語言環境。 匹配時只有語言環境會影響匹配結果。
re.M == re.MULTILINE
指定時,模式字符'^'匹配字符串的開頭和每行的開頭(緊跟在每個換行符之后); 并且模式字符'$'匹配字符串的末尾和每行末尾(緊接在每個換行符之前)。 默認情況下,'^'只匹配字符串的開頭,'$'只匹配字符串的末尾,緊接在字符串末尾的換行符(如果有的話)之前。 對應于內聯標志(?m)。
re.S == re.DOTALL
制作'.' 特殊字符完全匹配任何字符,包括換行符; 沒有這個標志,'.' 將匹配除換行符之外的任何內容。 對應于內聯標志(?s)。
re.X == re.VERBOSE
該標志允許您通過一種編輯模式來編寫正則表達式,允許您在視覺上分離模式的邏輯部分并添加注釋,該正則表達式看起來更好,并且更易讀。模式中的空格被忽略,除非在字符類中,或者前面有一個未轉義的反斜杠,或者在諸如 *?
, (?:
或 (?P<...>)
。我們還可以通過#號進行注釋。
這意味著匹配一個十進制數的下面兩個正則表達式對象在功能上是相等的:
a = re.compile(r"""\d + # the integral part
\. # the decimal point
\d * # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")
對應于內聯標志(?x)。
Match Objects 匹配的結果對象
我們之前就提到過當我們用寫好的規則去匹配一個字符串的時候,返回的不是匹配好的子字符串!而是返回一個匹配對象,里面除了包裝好子字符串,還提供其它的功能,因為,我們在寫規則的時候有通過捕獲括號對規則進行分組,因此,我們也可以通過匹配對象把分組給提取出來。比如hello(\w)
規則能夠匹配 'helloA',如果直接返回'helloA',那我們設置的捕獲括號不就失去意義了嗎,而匹配對象則正是能夠完成這些功能的關鍵所在。
匹配對象始終具有布爾值True。 由于match()和search()在不匹配時返回None,因此可以通過簡單的if語句測試是否成功匹配:
match = re.search(pattern, string)
if match:
process(match)
匹配對象支持以下方法和屬性:
match.expand(template)
我們設定一個模板,來顯示我們查找到的子字符串,其中,我們可以反斜杠替換的方式嵌入捕獲括號所匹配到的子字符串。
數字引用(\1, \2)或命名反斜線引用(\g<1>,\g <name>)被替換為相應組的內容。
比如我們要展示某個字符串中的數字,并列出個位數。
import re
p = re.compile(r'\d+(\d)(\d)')
match = p.search("adsfaddf12345fgd")
print(match .expand(r'The number in the string is \g<0>.\nThe last number is \2'))
The number in the string is 12345.
The last number is 5
match.group([group1, ...])
返回匹配的一個或多個子組。 如果只有一個參數,結果是一個單獨的字符串; 如果有多個參數,則結果是一個tuple。
沒有參數,group默認為零(整個匹配被返回)。 如果group參數為零,則相應的返回值是整個匹配的字符串; 如果參數在包含范圍[1..99]中,則它是匹配相應括號組的字符串。 如果組編號為負數或大于模式中定義的組數,則會引發IndexError異常。 如果一個組包含在不匹配的模式的一部分中,則相應的結果為無。 如果一個組包含在多次匹配的模式的一部分中,則返回最后的匹配。
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0) # 整個的匹配結果
'Isaac Newton'
>>> m.group(1) # 第一個括號匹配到的結果
'Isaac'
>>> m.group(2) # 第二個括號匹配到的結果
'Newton'
>>> m.group(1, 2) # 返回多個結果
('Isaac', 'Newton')
如果正則表達式使用 (?P<name>...)
語法,則group參數也可以是通過組名稱標識組的字符串。 如果字符串參數未在模式中用作組名稱,則會引發IndexError異常。
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'
命名組也可以通過它們的索引來引用:
>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'
如果一個組匹配多次,只能訪問最后一場匹配:
>>> m = re.match(r"(..)+", "a1b2c3") # 由于貪婪模式,這個表達式明顯匹配的結果是全部的字符串
# 但是其中的括號匹配到的是什么呢?
>>> m.group(1) # 返回最后能匹配到的
'c3'
讀者可以自行試試這個例子,看看 m.group() m.group(0) m.group(1) m.group(2) 分別是什么
match.getitem(g)
如果你熟悉python就知道這個屬性就像dict一樣,讓我們能夠直接用[]的形式取出數據,而不需要使用group函數,更加方便快捷。
這與m.group(g)相同。 這允許從比賽中更容易地訪問個人組:
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0] # 等價于 m.group(0)
'Isaac Newton'
>>> m[1] #
'Isaac'
>>> m[2] #
'Newton'
match.groups(default=None)
返回一個包含匹配所有子組的元組,從1開始,直到模式中有多個組。 default參數用于補齊沒能成功匹配的組; 它默認為None。
例如:
>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')
如果我們將小數點后的位置及其后的所有內容都設為可選,則并非所有組都可以參與該匹配。 除非給出默認參數,否則這些組將默認為None。
>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups() # 第二組默認為 None.
('24', None)
>>> m.groups('0') # Now, the second group defaults to '0'.
('24', '0')
match.groupdict(default=None)
返回包含匹配的所有命名子組的字典的子集名稱。 缺省參數用于未參與匹配的組; 它默認為None。 例如:
>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}
match.start([group])
match.end([group])
返回按組匹配的子串在原字符串中開始和結束的位置;
組默認為零,意味著整個匹配的子字符串,一個將從電子郵件地址中刪除remove_this的示例:
>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'tony@tiger.net'
>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.start(0) # 整個匹配到的是 "Isaac Newton" 其中"I"在原字符串的開頭,因此是 0
0
>>> m.end(0) # 整個匹配到的是 "Isaac Newton" 其中"n"在原字符串的第12位,因此是 12
12
>>> m.start(1) # 第一組匹配到的是 "Isaac" 其中"I"在原字符串的第0位,因此是 0
0
>>> m.end(1) # 第一組匹配到的是 "Isaac" 其中"c"在原字符串的第5位,因此是 5
5
>>> m.start(2) # 第2組匹配到的是 "Newton" 其中"N"在原字符串的第6位,因此是 6
6
>>> m.end(2) # 第2組匹配到的是 "Newton" 其中"n"在原字符串的第12位,因此是 12
12
match.span([group])
對于匹配m,返回2元組(m.start(group),m.end(group))。 請注意,如果組沒有參與匹配,則為(-1,-1)。 組默認為零。對于上面的例子 m.span() 也就是 m.span(0) 返回 (0,12) m.span(1) 返回 (0,5) m.group(2) 返回 (6,12)
Match Object的一些參數
match.pos
傳遞給正則表達式對象的search()或match()方法的pos的值。 這是RE引擎開始尋找匹配的字符串的索引。根據此值,我們能夠知道在引擎搜索時設定的pos
match.endpos
傳遞給正則表達式對象的search()或match()方法的endpos的值。 這是RE引擎不會去的字符串的索引。
match.lastindex
最后一個匹配捕獲組的整數索引,或者如果沒有匹配組,則為None。 例如,如果將表達式(a)b,((a)(b))和((ab))應用于字符串"ab",則lastindex == 1,而表達式(a)(b)將 如果應用于相同的字符串,則lastindex == 2。
match.lastgroup
最后匹配的捕獲組的名稱,如果組沒有名稱,或者根本沒有匹配組,則為None。
match.re
正則表達式對象的match()或search()方法生成此匹配實例。
match.string
傳遞給match()或search()的字符串。
一些例子
尋找對子
在這個例子中,我們將使用以下輔助函數來更加優雅地顯示匹配對象:
def displaymatch(match):
if match is None:
return None
return '<Match: %r, groups=%r>' % (match.group(), match.groups())
假設您正在編寫一個撲克程序,其中玩家的手牌為5個字符的字符串,每個字符代表一張牌,"a"代表王牌,"k"代表國王,"q"代表女王,"j"代表插孔, "t"為10,"2"至"9"代表具有該值的卡。
要查看給定的字符串是否是有效的,可以執行以下操作:
>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q")) # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e")) # Invalid.
>>> displaymatch(valid.match("akt")) # Invalid.
>>> displaymatch(valid.match("727ak")) # Valid.
"<Match: '727ak', groups=()>"
最后一只手牌,"727ak",包含一對,或兩個相同的價值卡。 為了與正則表達式匹配,可以使用反斜線引用:
>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak")) # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak")) # No pairs.
>>> displaymatch(pair.match("354aa")) # Pair of aces.
"<Match: '354aa', groups=('a',)>"
為了找出這對卡片組成的卡片,可以按照以下方式使用匹配對象的group()方法:
>>> pair.match("717ak").group(1)
'7'
# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'
>>> pair.match("354aa").group(1)
'a'
search() vs. match()
Python提供了基于正則表達式的兩種不同的基本操作:re.match()僅在字符串的開始處檢查匹配,而re.search()檢查字符串中任意位置的匹配(這是Perl默認執行的操作)。
例如:
>>> re.match("c", "abcdef") # No match
>>> re.search("c", "abcdef") # Match
<re.Match object; span=(2, 3), match='c'>
以'^'開頭的正則表達式可以與search()一起用于限制字符串開始處的匹配:
>>> re.match("c", "abcdef") # No match
>>> re.search("^c", "abcdef") # No match
>>> re.search("^a", "abcdef") # Match
<re.Match object; span=(0, 1), match='a'>
但是請注意,在MULTILINE模式下match()只匹配字符串的開頭,而使用search()與以'^'開頭的正則表達式匹配每行的開頭。
>>> re.match('X', 'A\nB\nX', re.MULTILINE) # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE) # Match
<re.Match object; span=(4, 5), match='X'>
做一個電話本
split()將字符串分割成由正則表達式分隔的列表。該方法對于將文本數據轉換為可由Python輕松讀取和修改的數據結構非常有用,如以下創建電話簿的示例所示。
首先,這是輸入。通常它可能來自一個文件:
>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""
條目由一個或多個換行符分隔。 現在我們將字符串轉換為一個列表,每個非空行都有自己的條目:
>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
最后,將每個條目分成一個名字,姓氏,電話號碼和地址。 因為地址中有空格,為了不把地址分隔開,我們使用split()的maxsplit參數,:
>>> [re.split(":? ", entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]
我們可以將最多的房屋號碼與街道名稱分開:
>>> [re.split(":? ", entry, 4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]
Text Munging
Mung或munge是計算機術語,用于對一段數據或文件進行一系列潛在的破壞性或不可撤銷的更改。它有時用于說話人尚不清楚的模糊數據轉換步驟。常見的搜索操作包括刪除標點或html標簽,數據解析,過濾和轉換。 [wiki]
sub()用一個字符串或一個函數的結果替換每個匹配規則的子部分。 這個例子演示了如何使用sub()和函數來"munge"文本,或者隨機化除了第一個和最后一個字符之外的每個單詞中所有字符的順序:
>>> def repl(m):
... inner_word = list(m.group(2))
... random.shuffle(inner_word)
... return m.group(1) + "".join(inner_word) + m.group(3)
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'
尋找所有的動詞
findall()匹配所有匹配的子部分,而不僅僅是search()所做的第一個。 例如,如果一個人是作家,并且想要在某些文本中找到所有副詞,他或她可以按以下方式使用findall():
>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']
尋找所有的動詞和對應位置
如果想要獲得關于匹配文本的所有匹配的更多信息,finditer()很有用,因為它提供了匹配對象而不是字符串。 繼續前面的例子,如果一個作家想要在某些文本中找到所有副詞及其位置,他或她會按以下方式使用finditer():
>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly
原始字符串符號
原始字符串符號(r"text")使正則表達式保持正常。 沒有它,正則表達式中的每個反斜杠( '' )必須以另一個反斜杠作為前綴。 例如,以下兩行代碼在功能上是相同的:
>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
當想要匹配文字反斜杠時,它必須在正則表達式中轉義。 用原始字符串表示法,這意味著r"\"。 沒有原始字符串表示法,必須使用"\\",使以下代碼行功能相同:
>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
總而言之,最好使用raw字符串
寫一個分詞器
分詞器或掃描器分析字符串以對字符組進行分類。這是編寫編譯器或解釋器的第一步。所以,理論上來說,學完re模塊就可以寫一個屬于自己的計算機語言,努力吧,說不定你的語言會成為下一個python,成為廣受歡迎的語言。
下面就是告訴大家如何寫一個分詞器,讓你的編譯器識別代碼中的關鍵字,識別符,值等等元素。
編譯器首先需要把代碼中的一個個詞拆開分類為一個個token
一個token有'type' 屬性,'value'屬性,'line'屬性,'column'屬性
一條代碼 'if a > 1:' 就會被分為5個token
第一個token, 'type' 是關鍵字 , 'value'是IF, 'line'是該語句所在的行數, 'column'是 if在該行從左往右數的位置
第二個token, 'type'是 標識符(ID) 'value'是 a,
第三個token, 'type'是 操作符(Operator) 'value'是 '>'
第四個token, 'type'是 數量(Number) 'value'是 1
第五個token, 'type'是 判斷結束符(EndOfIf) 'value'是 :
就這樣把代碼拆開,準確地識別各個token,是每一個編譯器工作的第一步。
token的結構可以由你自己定義,但一般來說都要都有type屬性和value屬性,你還可以自己添加其他的屬性來方便編譯器工作,這取決于你的編程水平。
import collections
import re
# 這是一個簡單的定義一個class的方法,我們定義一個token類,我們把代碼拆開然后根據正則表達式的識別將其實例化為一個個token
Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])
def tokenize(code):
# 定義我們的編譯器有哪些關鍵字,不同的語言關鍵字是不一樣的,需要語言開發者自行定義
keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
# 語言除了關鍵字,還有其他的類型
token_specification = [
# 定義我們語言能使用的數值
('NUMBER', r'\d+(\.\d*)?'), # Integer or decimal number
# 定義語言的賦值符號
('ASSIGN', r':='), # Assignment operator
#定義語句的結束標識,一般來說都是分號,我們也用分號來標識結尾吧
('END', r';'), # Statement terminator
#標識符,比如變量的名字,就是一個標識符,我們這里規則變量只能用字母連下劃線都不能用
('ID', r'[A-Za-z]+'), # Identifiers
#定義運算符,我們的語言能進行+-*/
('OP', r'[+\-*/]'), # Arithmetic operators
#要能夠識別新的一行代碼
('NEWLINE', r'\n'), # Line endings
#要能夠識別各種空白
('SKIP', r'[ \t]+'), # Skip over spaces and tabs
#其他任何的東西我們都是別錯誤匹配,相當于寫了錯誤的語句,我們的語言要報錯,比如你在python里面寫prinf("hello")肯定是錯誤的,這是C里面的語句
('MISMATCH',r'.'), # Any other character
]
#我們把上面的正則表達式用或串起來
tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
line_num = 1
line_start = 0
# 我們開始對代碼進行識別
for mo in re.finditer(tok_regex, code):
kind = mo.lastgroup
value = mo.group(kind)
if kind == 'NEWLINE':
line_start = mo.end()
line_num += 1
elif kind == 'SKIP':
pass
elif kind == 'MISMATCH':
raise RuntimeError(f'{value!r} unexpected on line {line_num}')
else:
if kind == 'ID' and value in keywords:
kind = value
column = mo.start() - line_start
yield Token(kind, value, line_num, column)
statements = '''
IF quantity THEN
total := total + price * quantity;
tax := price * 0.05;
ENDIF;
'''
# 把我們實例化的token打印出來
for token in tokenize(statements):
print(token)
分詞器產生以下輸出:
Token(type='IF', value='IF', line=2, column=4)
Token(type='ID', value='quantity', line=2, column=7)
Token(type='THEN', value='THEN', line=2, column=16)
Token(type='ID', value='total', line=3, column=8)
Token(type='ASSIGN', value=':=', line=3, column=14)
Token(type='ID', value='total', line=3, column=17)
Token(type='OP', value='+', line=3, column=23)
Token(type='ID', value='price', line=3, column=25)
Token(type='OP', value='*', line=3, column=31)
Token(type='ID', value='quantity', line=3, column=33)
Token(type='END', value=';', line=3, column=41)
Token(type='ID', value='tax', line=4, column=8)
Token(type='ASSIGN', value=':=', line=4, column=12)
Token(type='ID', value='price', line=4, column=15)
Token(type='OP', value='*', line=4, column=21)
Token(type='NUMBER', value='0.05', line=4, column=23)
Token(type='END', value=';', line=4, column=27)
Token(type='ENDIF', value='ENDIF',line=5, column=4)
Token(type='END', value=';', line=5, column=9)
'