[TOC] 引言 剛接觸正則表達式,我也曾被它們天書似的符號組合給嚇住,但經過一段時間的深入學習,發現它並沒有想象中那麼可怕,只要多實踐,多理解,也是可以輕鬆搞定的。 而且我發現帶著問題去學習,求知欲會驅使著你往前走,不知不覺就懂了。 下麵就是我在學習中提出的幾個問題,在後面會依次進行討論。由於正則 ...
目錄
引言
剛接觸正則表達式,我也曾被它們天書似的符號組合給嚇住,但經過一段時間的深入學習,發現它並沒有想象中那麼可怕,只要多實踐,多理解,也是可以輕鬆搞定的。
而且我發現帶著問題去學習,求知欲會驅使著你往前走,不知不覺就懂了。
下麵就是我在學習中提出的幾個問題,在後面會依次進行討論。由於正則表達式涉及到的內容確實非常多,分成兩篇來闡述。
- 什麼是正則表達式?
- 正則表達式可以乾什麼?
- 正則表達式的語法以及在 python 中這些語法是如何使用的?
- 正則表達式如何處理中文字元?
- python 的正則表達式庫中有哪些重要的函數?
什麼是正則表達式?
正則表達式使用單個字元串來描述,匹配一系列符合某個句法規則的字元串。
— 維基百科
先來劃重點:
- 正則表達式的表現形式是 單個字元串
- 它用來執行匹配的動作
- 匹配的對象也是字元串
語言總是有些蒼白的,必須要結合實例才能理解的更清楚,先來看一個例子:
>>> import re
>>>re.search(r'wo\w+d', 'hello world!')
<re.Match object; span=(6, 11), match='world'>
>>>
這裡先概略說明 re.search
方法:引入的 re
模塊就是 python 的正則表達式模塊,re.search
函數目的就是接受一個正則表達式和一個字元串,並以 Match
對象的形式返回匹配的第一個元素。如果沒有匹配到,則會返回 None
。(關於 search
函數先瞭解這些就可以,後面會有詳細講解。)
下麵就拿這個示例中 re.search
中的參數來匹配下上面的概念,加深一下理解
- 'wo\w+d' 就是正則表達式,它還有一個名稱叫做_模式(pattern)_ ,表示
wo
字母後有多個字母並一直到d
字母出現為止(現在不明白沒關係,只要知道它就是正則表達式就可以了,後面會詳細講) - 在 'wo\w+d' 前面還有一個
r
表示什麼呢?這個r
表示 raw的意思,就是原始字元串。原始字元串不會將\
解釋成一個轉義字元,而是這樣做對正則表達式好處是大大的,只有這樣\w
才能起作用。 - 'hello world!' 就是要匹配的字元串。
- 整個函數就表示從 'hello world!' 字元串中搜索出符合_'wo\w+d'_ 模式的字元串,並展示出來,於是
world
字元串就被篩選了出來。
正則表達式有什麼用?
我們學習正則表達式的目的是什麼?當然是為了有朝一日能使用它解決我們面臨的問題,要不然,學它幹嘛。那我們就來聊聊正則表達式的用途:
字元串驗證
你肯定在網頁上註冊過賬號吧,假如你在註冊 github 網站,它要求你輸入 Email,而你卻胡亂填寫了幾個數字就想註冊,這時就會彈出提示 "Email is invalid",意思就是你的郵箱是無效的,這就是正則表達式的功勞。
替換文本
假如你正在寫一篇關於 java 的文章,寫著寫著,你覺得換成 python 更好些,你想一下把出現 java , Java 的地方全都替換成 python , 正則表達式可以幫你做到。
從字元串中提取出要獲取的字元串
假如你正在爬取一個汽車排行榜頁面,想要獲取每個車型的編號,而車型編號則隱藏在鏈接中,怎麼獲取呢?用正則表達式可以。
正則表達式的語法及使用實例
對剛接觸的同學來說,正則表達式的語法很晦澀。不用擔心,我們先大致瀏覽一下完整的語法組合,後面再使用前面講過的 re.search
方法一個個詳細介紹,講著講著,我相信你就明白了。
正則表達式語法有哪些?
字元 | 功能描述 |
---|---|
\ |
特殊字元轉義 |
^ |
匹配字元串的開始位置 |
$ |
匹配字元串的結束位置 |
* |
匹配前面的子表達式零次或多次 |
+ |
匹配前面的子表達式一次或多次 |
? |
匹配前面的子表達式零次或一次 |
{n} |
n是非負整數,匹配n次 |
{n,} |
n 是非負整數,匹配 >=n 次 |
{n,m} |
m是非負整數,n<=m, 匹配>= n 並且 <=m 次 |
? |
非貪心量化 |
. |
匹配除“\r ”“\n ”之外的任何單個字元 |
(pattern) |
匹配pattern並獲取這一匹配的子字元串 |
(?:pattern) |
非獲取匹配 |
(?=pattern) |
正向肯定預查 |
(?!pattern) |
正向否定預查 |
(?<=pattern) |
反向(look behind)肯定預查 |
(?<!pattern) |
反向否定預查 |
x|y |
沒有包圍在()里,其範圍是整個正則表達式 |
[xyz] |
字元集合(character class),匹配所包含的任意一個字元 |
[^xyz] |
排除型字元集合(negated character classes),匹配未列出的任意字元 |
[a-z] |
字元範圍,匹配指定範圍內的任意字元 |
[^a-z] |
排除型的字元範圍,匹配任何不在指定範圍內的任意字元 |
\b |
匹配一個單詞邊界,也就是指單詞和空格間的位置 |
\B |
匹配非單詞邊界 |
\cx |
匹配由x指明的控制字元 |
\d |
匹配一個數字字元。等價於[0-9] |
\D |
匹配一個非數字字元。等價於[^0-9] 。 |
\f |
匹配一個換頁符。等價於\x0c和\cL。 |
\n |
匹配一個換行符。等價於\x0a和\cJ。 |
\r |
匹配一個回車符。等價於\x0d和\cM。 |
\s |
匹配任何空白字元。等價於[ \f\n\r\t\v] |
\S |
匹配任何非空白字元。等價於[^ \f\n\r\t\v] 。 |
\t |
匹配一個製表符。等價於\x09和\cI。 |
\v |
匹配一個垂直製表符。等價於\x0b和\cK。 |
\w |
匹配包括下劃線的任何單詞字元。等價於“[A-Za-z0-9_] ” |
\W |
匹配任何非單詞字元。等價於“[^A-Za-z0-9_] ”。 |
\ck |
匹配控制轉義字元。k代表一個字元。等價於“Ctrl-k ” |
\xnn |
十六進位轉義字元序列 |
\n |
標識一個八進位轉義值或一個向後引用 |
\un |
Unicode轉義字元序列 |
這些正則到底該怎麼用?
瀏覽一遍,感覺怎麼樣,是不是摩拳擦掌,想要立刻實踐一番,嘿嘿。好的我們現在就開乾。
^
匹配字元串的開始位置>>> import re >>> re.search(r'^h', 'he is a hero!') <re.Match object; span=(0, 1), match='h'>
這個例子中雖然有兩個 h,因為前面有
^
所以只會匹配第一個$
匹配字元串的結束位置>>> import re >>> re.search(r't$','this is an object') <re.Match object; span=(16, 17), match='t'>
雖然這個句子前後都有 t,卻是最後的被匹配到了
*
匹配前面的子表達式 0 次或多次,例如,"zo*" 能匹配"z","zo","zoo",我們來驗證下>>> import re >>> re.search(r'zo*', 'z') <re.Match object; span=(0, 1), match='z'> >>> re.search(r'zo*', 'zo') <re.Match object; span=(0, 2), match='zo'> >>> re.search(r'zo*', 'zoo') <re.Match object; span=(0, 3), match='zoo'>
這裡
*
還有一種寫法 {0,},兩者等價。其中{}
叫做重覆。來看例子。import re re.search(r'zo{0,}','z') <_sre.SRE_Match object; span=(0, 1), match='z'> re.search(r'zo{0,}','zo') <_sre.SRE_Match object; span=(0, 2), match='zo'> re.search(r'zo{0,}','zoo') <_sre.SRE_Match object; span=(0, 3), match='zoo'>
+
匹配前面的子表達式一次或多次,以 "zo+" 為例,它能匹配 "zo","zoo",來驗證下>>> import re >>> re.search(r'zo+', 'zo') <re.Match object; span=(0, 2), match='zo'> >>> re.search(r'zo+', 'zoo') <re.Match object; span=(0, 3), match='zoo'>
這裡
+
還有一種寫法{1,}
兩者是等價的,來看例子。>>> import re >>> re.search(r'zo{1,}','zo') <re.Match object; span=(0, 2), match='zo'> >>> re.search(r'zo{1,}','zoo') <re.Match object; span=(0, 3), match='zoo'>
?
匹配前面的子表達式0次或 1次,以 "ab(cd)?" 為例,可以匹配 "ab","abcd",看下麵例子import re re.search(r'ab(cd)?','ab') <_sre.SRE_Match object; span=(0, 2), match='ab'> re.search(r'ab(cd)?','abcd') <_sre.SRE_Match object; span=(0, 4), match='abcd'>
這裡
?
還有一種寫法{0,1}
兩者等價,看下麵import re re.search(r'ab(cd){0,1}', 'ab') <_sre.SRE_Match object; span=(0, 2), match='ab'> re.search(r'ab(cd){0,1}', 'abcd') <_sre.SRE_Match object; span=(0, 4), match='abcd'>
{n}
n 必須是非負整數,能匹配確定的 n 次,以 "o{2}" 為例,它能匹配 "good", 卻不能匹配 "god"import re re.search(r'o{2}', 'god') re.search(r'o{2}', 'good') <_sre.SRE_Match object; span=(1, 3), match='oo'>
{n,}
n是一個非負整數。至少能匹配 n次。例如,“o{2,}”不能匹配 “god”中的 “o”,但能匹配“foooood”中的所有o。“o{1,}”等價於“o+”。“o{0,}”則等價於“o*”,這個可看前面示例。{n,m}
m和n均為非負整數,其中n<=m。例如,“o{1,2}”將匹配“google”中的兩個o。“o{0,1}”等價於“o?”。註意在逗號和兩個數之間不能有空格re.search(r'o{1,2}', 'google') <_sre.SRE_Match object; span=(1, 3), match='oo'>
?
這個叫做非貪心量化(Non-greedy quantifiers),這個字元和前面的?
有什麼區別?應用場合是什麼呢?當該字元緊跟在任何一個其他重覆修飾符(*,+,?,{n},{n,},{n,m})後面時,匹配模式是非貪婪的。非貪婪模式儘可能少的匹配所搜索的字元串,而預設的貪婪模式則儘可能多的匹配所搜索的字元串。舉個例子,"o+" 預設會匹配 o 一次或多次,如果在後面加上 "?",則匹配一次。來看代碼。
re.search(r'o+?', 'google') <_sre.SRE_Match object; span=(1, 2), match='o'> re.search(r'o+', 'google') <_sre.SRE_Match object; span=(1, 3), match='oo'>
.
匹配除了\r
,\n
之外的任何單個字元,要匹配包括“\r
”“\n
”在內的任何字元,請使用像“(.|\r|\n)
”的模式import re re.search(r'a.', 'ab') <_sre.SRE_Match object; span=(0, 2), match='ab'>
(pattern)
匹配 pattern 並獲取這一匹配的子字元串,並用於向後引用。使用圓括弧可以指定分組。當使用分組時,除了獲取到整個匹配的完整字元串,也可以從匹配中選擇每個單獨的分組。下麵給出一個本地電話號碼的示例,其中每個括弧內匹配的數字都是一個分組。
>>> import re >>> match = re.search(r'([\d]{3,4})-([\d]{7,8})', '010-12345678') >>> match <re.Match object; span=(0, 12), match='010-12345678'> >>> match.group(1) '010' >>> match.group(2) '12345678' >>> match.group() '010-12345678' >>> match.groups() ('010', '12345678')
前面我們只是簡單介紹了
match
對象,為了深入的理解分組,這裡還要簡單介紹下該對象的幾個方法以及如何對應分組信息的:groups()
用於返回一個對應每一個單個分組的元組。>>> match.groups() ('010', '12345678')
group()
方法(不含參數)則返回完整的匹配字元串>>> match.group() '010-12345678'
group(num)
num 是分組編號,按照分組順序,從 1 開始取值,能獲取具體的分組數據。>>> match.group(1) '010' >>> match.group(2) '12345678'
(?:pattern)
匹配 pattern 但不獲取匹配的子字元串(shy groups),也就是說這是一個非獲取匹配,不存儲匹配的子字元串用於向後引用。這種格式的圓括弧不會作為分組信息,只用於匹配,即在python 調用search
方法而得到的match
對象不會將圓括弧作為分組存儲起來。來看下麵例子,只獲取電話號,而不獲取地區區號。
>>> match = re.search(r'(?:[\d]{3,4})-([\d]{7,8})', '010-12345678') >>> match.groups() ('12345678',) >>> match.group() '010-12345678'
這種形式對使用 或 字元`(|)”來組合一個模式的各個部分是很有用的,來看一個例子,想要同時匹配 city 和 cities (複數形式),就可以這樣乾
>>> match = re.search(r'cit(?:y|ies)','cities') >>> match <re.Match object; span=(0, 6), match='cities'> >>> match = re.search(r'cit(?:y|ies)','city') >>> match <re.Match object; span=(0, 4), match='city'>
(?=pattern)
正向肯定預查(look ahead positive assert),在任何匹配 pattern 的字元串開始處匹配查找字元串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以後使用。舉個例子,假設我要獲取從不同 python 版本中只獲取 "python" 字元串,就可以這樣寫:
>>> match = re.search(r'python(?=2.7|3.5|3.6|3.7)', 'python3.7') >>> match <re.Match object; span=(0, 6), match='python'>
預查不消耗字元,也就是說,在一個匹配發生後,在最後一次匹配之後立即開始下一次匹配的搜索,而不是從包含預查的字元之後開始。那麼在 python 版本後再加上其他信息,整體就無法匹配了。
看下麵例子,得到的結果只能是 null。
>>> match = re.search(r'python(?=2.7|3.5|3.6|3.7) is hacking!', 'python3.7 is hacking!') >>> match
(?!pattern)
正向否定預查(negative assert),看名字也知道是 正向肯定預查的反面。在任何不匹配 pattern 的字元串開始處匹配查找字元串。是一個非獲取匹配,而且預查不消耗字元。看下麵例子,和正向肯定預查一對比就明白了。
>>> match = re.search(r'python(?!2.7|3.5|3.6|3.7)', 'python3.7') >>> match >>> match = re.search(r'python(?!2.7|3.5|3.6|3.7)', 'python3.1') >>> match <re.Match object; span=(0, 6), match='python'> >>> match = re.search(r'python(?!2.7|3.5|3.6|3.7) is hacking!', 'python3.1 is hacking!') >>> match
(?<=pattern)
反向(look behind)肯定預查,與正向肯定預查類似,只是方向相反。(?<!pattern)
反向否定預查,與正向否定預查類似,只是方向相反。x|y
或,分兩種情況:沒有沒括弧包圍,範圍則是整個表達式;被括弧包圍,返回是括弧內。>>> match = re.search(r'today is sunday|tommory is monday','tommory is monday') >>> match <re.Match object; span=(0, 17), match='tommory is monday'>
[xyz]
字元集合,匹配所包含的任意一個字元。分為下麵情況普通字元
>>> match = re.search(r'[Pp]ython','python') >>> match <_sre.SRE_Match object; span=(0, 6), match='python'>
特殊字元僅有反斜線
\
保持特殊含義,用於轉義字元其它特殊字元如星號、加號、各種括弧等均作為普通字元
>>> match = re.search(r'[*?+()]python','*python') >>> match <_sre.SRE_Match object; span=(0, 7), match='*python'> >>> match = re.search(r'[*?+()]python','+python') >>> match <_sre.SRE_Match object; span=(0, 7), match='+python'> >>> match = re.search(r'[*?+()]python','(python') >>> match <_sre.SRE_Match object; span=(0, 7), match='(python'>
^
出現在字元串中間和末尾僅作為普通字元,出現在最前面後面會講。>>> match = re.search(r'[*^{]python','^python') >>> match <_sre.SRE_Match object; span=(0, 7), match='^python'> >>> match = re.search(r'[*^]python','^python') >>> match <_sre.SRE_Match object; span=(0, 7), match='^python'>
-
出現在字元集合首位和末尾,僅作為普通字元,出現在中間是字元範圍描述,後面會講。>>> match = re.search(r'[-^]python','-python') >>> match <_sre.SRE_Match object; span=(0, 7), match='-python'>
[^xyz]
排除型字元集合(negated character classes)。匹配未列出的任意字元>>> re.search(r'[^abc]def','edef') <re.Match object; span=(0, 4), match='edef'>
[a-z]
字元範圍。匹配指定範圍內的任意字元。>>> re.search(r'[a-g]bcd','ebcd') <re.Match object; span=(0, 4), match='ebcd'> >>> re.search(r'[a-g]bcd','hbcd')
[^a-z]
排除型的字元範圍。匹配任何不在指定範圍內的任意字元。>>> re.search(r'[^a-g]bcd','hbcd') <re.Match object; span=(0, 4), match='hbcd'>
\b
匹配一個單詞邊界,也就是指單詞和空格間的位置。>>> re.search(r'an\b apple','an apple') <re.Match object; span=(0, 8), match='an apple'>
\B
匹配非單詞邊界>>> re.search(r'er\B','verb') <re.Match object; span=(1, 3), match='er'>
\d
匹配一個數字字元。等價於[0-9]>>> re.search(r'\d apples', '3 apples') <re.Match object; span=(0, 8), match='3 apples'>
\D
匹配一個非數字字元。等價於[^0-9]
>>> re.search(r'\D dog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\s
匹配任何空白字元,包括空格、製表符、換頁符等等。等價於[ \f\n\r\t\v]。>>> re.search(r'a\sdog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\S
匹配任何非空白字元。等價於[^ \f\n\r\t\v]
>>> re.search(r'\S dog', 'a dog') <re.Match object; span=(0, 5), match='a dog'>
\w
匹配包括下劃線的任何單詞字元。等價於“[A-Za-z0-9_]
”>>> re.search(r'\w', 'h') <re.Match object; span=(0, 1), match='h'>
\W
匹配任何非單詞字元。等價於“[^A-Za-z0-9_]
”>>> re.search(r'\W', '@') <re.Match object; span=(0, 1), match='@'>
\un
Unicode轉義字元序列。其中n是一個用四個十六進位數字表示的Unicode字元>>> re.search(r'\u00A9','©') <re.Match object; span=(0, 1), match='©'>
小結
如果你真的讀完了這些實例,我敢說你對正則表達式會有一定的理解了吧。下篇會重點講解python 中的正則表達式庫函數,對中文的處理等,敬請期待~
參考文檔
系列文章列表
- python 歷險記(一)—String,集合(List,元組,Dict)
- python 歷險記(二)— python 的面向對象
- python 歷險記(三)— python 的常用文件操作
- python 歷險記(四)— python 中常用的 json 操作
- python 歷險記(五)— python 中的模塊