python 歷險記(六)— python 對正則表達式的使用(上篇)

引言

剛接觸正則表達式,我也曾被它們天書似的符號組合給嚇住,但通過一段時間的深刻學習,發現它並無想象中那麼可怕,只要多實踐,多理解,也是能夠輕鬆搞定的。
並且我發現帶着問題去學習,求知慾會驅使着你往前走,不知不覺就懂了。
下面就是我在學習中提出的幾個問題,在後面會依次進行討論。因爲正則表達式涉及到的內容確實很是多,分紅兩篇來闡述。java

  1. 什麼是正則表達式?
  2. 正則表達式能夠幹什麼?
  3. 正則表達式的語法以及在 python 中這些語法是如何使用的?
  4. 正則表達式如何處理中文字符?
  5. python 的正則表達式庫中有哪些重要的函數?

什麼是正則表達式?

正則表達式使用單個字符串來描述,匹配一系列符合某個句法規則的字符串。python

— 維基百科git

先來劃重點:github

  1. 正則表達式的表現形式是 單個字符串
  2. 它用來執行匹配的動做
  3. 匹配的對象也是字符串

語言老是有些蒼白的,必需要結合實例才能理解的更清楚,先來看一個例子:正則表達式

>>> 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\w+d' 就是正則表達式,它還有一個名稱叫作_模式(pattern)_ ,表示wo 字母后有多個字母並一直到d 字母出現爲止(如今不明白不要緊,只要知道它就是正則表達式就能夠了,後面會詳細講
  • 'wo\w+d' 前面還有一個 r 表示什麼呢?這個 r 表示 raw的意思,就是原始字符串。原始字符串不會將 \ 解釋成一個轉義字符,而是這樣作對正則表達式好處是大大的,只有這樣 \w 才能起做用。
  • 'hello world!' 就是要匹配的字符串。
  • 整個函數就表示從 'hello world!' 字符串中搜索出符合_'wo\w+d'_ 模式的字符串,並展現出來,因而 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 中的正則表達式庫函數,對中文的處理等,敬請期待~

參考文檔

  1. 維基百科—正則表達式

系列文章列表

相關文章
相關標籤/搜索