剛接觸正則表達式,我也曾被它們天書似的符號組合給嚇住,但通過一段時間的深刻學習,發現它並無想象中那麼可怕,只要多實踐,多理解,也是能夠輕鬆搞定的。
並且我發現帶着問題去學習,求知慾會驅使着你往前走,不知不覺就懂了。
下面就是我在學習中提出的幾個問題,在後面會依次進行討論。因爲正則表達式涉及到的內容確實很是多,分紅兩篇來闡述。java
正則表達式使用單個字符串來描述,匹配一系列符合某個句法規則的字符串。python
— 維基百科git
先來劃重點:github
語言老是有些蒼白的,必需要結合實例才能理解的更清楚,先來看一個例子:正則表達式
>>> 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
函數先了解這些就能夠,後面會有詳細講解。)shell
下面就拿這個示例中 re.search
中的參數來匹配下上面的概念,加深一下理解json
wo
字母后有多個字母並一直到d
字母出現爲止(如今不明白不要緊,只要知道它就是正則表達式就能夠了,後面會詳細講)r
表示什麼呢?這個 r
表示 raw的意思,就是原始字符串。原始字符串不會將 \
解釋成一個轉義字符,而是這樣作對正則表達式好處是大大的,只有這樣 \w
才能起做用。world
字符串就被篩選了出來。咱們學習正則表達式的目的是什麼?固然是爲了有朝一日能使用它解決咱們面臨的問題,要否則,學它幹嗎。那咱們就來聊聊正則表達式的用途:app
字符串驗證函數
你確定在網頁上註冊過帳號吧,假如你在註冊 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 中的正則表達式庫函數,對中文的處理等,敬請期待~