Python 之 RE(正規表示式)常用

Galois發表於2020-03-16

正規表示式基礎

提取字串

語法 說明 可匹配字串
. 匹配除了換行符”\n”以外的任意字元 a.b acb、adb、a2b、a~b
\ 轉義,將跳脫字元後面的一個字元改變成原來的意思 a[b\.\\]c abc、a.c、a\c
[] 匹配括號內的任意字元 a[b,c,d,e]f abd、acf、adf、aef

預定義字元

語法 說明 可匹配字串
^ 以說明字串開始 ^123 123abc、123321、123zxc
$ 以說明字串結尾 123$ abc123、321123、zxc123
\b 匹配單詞邊界,不匹配任何字元 \basd\b asd
\d 匹配數字 0~9 zx\dc zx1c、zx2c、zx5c
\D 匹配非數字 zx\Dc zxvc、zx$c、zx&c
\s 匹配空白符 zx\sc zx c
\S 匹配非空白符 zx\Sc zxac、zx1c、zxtc
\w 匹配字母、數字和下劃線 zx\wc zxdc、zx1c、zx_c

注意:

  • \b 匹配的只是一個位置,這個位置的一側是構成單詞的字元,另一側為非單詞字元、字串的開始或結束位置。\b 是零寬度。
  • \w 在不同編碼語言中匹配的範圍是不一樣的,在使用 ASCII 碼的語言中匹配的是 [a-zA-Z0-9] ,而在使用 Unicode 碼的語言中匹配的是 [a-zA-Z0-9] 和漢字、全形符號等特殊字元。

數量限定模式

語法 說明 可匹配字串
* 匹配 0 次到多次 zxc* zx、zxccccc
+ 匹配 1 次到多次 zxc+ zxc、zxccccc
? 匹配 0 次或 1 次 zxc? zxc、zx
{m} 匹配 m 次 zxc{3}vb zxcccvb
{m,} 匹配 m 次或多次 zxc{3,}vb zxcccvb、zxccccccccvb
{,n} 匹配 0 次到 n 次 zxc{,3}vb zxvb、zxcvb、zxccvb、zxcccvb

零寬斷言

指的是當斷言表示式為 True 時才進行匹配,但是並不匹配斷言表示式內容。和 ^ 代表開頭, $ 代表結尾, \b 代表單詞邊界一樣,先行斷言和後行斷言也有類似的作用,它們只匹配某些位置,在匹配過程中,不佔用字元,所以被稱為零寬。所謂位置,是指字串中第一個字元的左邊、最後一個字元的右邊以及相鄰字元的中間。零寬斷言表示式有四種:
零寬度負回顧後發斷言 (?<!exp),表示式不成立時匹配斷言後面的位置,成立時不匹配。例如 \w+(?\<zxc)\d,匹配不以 zxc 結尾的字串;零寬度負回顧先行斷言 (?!exp),表示式匹配斷言前面的位置,成立時則不匹配。例如:\d(?!zxc)\w+,匹配不以 zxc 開頭的字串;先行斷言 (?=exp),斷言為真時匹配斷言前面的位置,例如要在 “a regular expression” 這個字串中追匹配出 regular 中的 re ,我們可以這麼寫 re(?=gular);後發斷言 (?<=exp),斷言為真時匹配斷言後面的位置,例如對 “egex represents regular expression” 這個字串要想匹配除 regex 和 regular 之外的 re,可以用 re(?!g),該表示式限定了re右邊的位置,這個位置後面不是字元g。先行和後發的區別就在於該位置之後的字元能否匹配括號中的表示式。

貪婪 / 非貪婪 模式

正規表示式會盡可能多的去匹配字元,這被稱為貪婪模式,貪婪模式是正規表示式預設的模式。但是有時候貪婪模式會給我們造成不必要的困擾,例如我們要匹配字串 “Jack123Chen123Chen” 中的 “Jack123Chen”,但是貪婪模式匹配出的卻是 “Jack123Chen123Chen”,這時我們就需要用到非貪婪模式來解決這個問題,非貪婪模式常用的表示式:

語法 說明
*? 匹配 0 次或多次,但要儘可能少重複
+? 匹配 1 次或多次,但要儘可能少重複
?? 匹配 0 次或 1 次,但要儘可能少重複
{m,}? 匹配 m 次或多次,但要儘可能少重複

OR 匹配又稱匹配分支,也就是說只要有一個分支匹配就算匹配,這和我們在開發中使用的 OR 語句類似。OR 匹配利用 | 分割分支,例如我們需要匹配出英文姓名,但是在英文中姓和名中間有可能是以 · 分割,也有可能是以空格分隔,這時我們就可以利用 OR 匹配來處理這個問題。格式如下:[A-Za-z]+·[A-Za-z]+|[A-Za-z]+\s[A-Za-z]+
組合,將幾個項組合為一個單元,這個單元可透過 * + ? | 等符號加以修飾,而且可以記住和這個組合相匹配的字串以提供伺候的引用使用。分組使用 () 來表示。例如獲取日期的正規表示式可以這麼寫:\d{4}-(0[1-9]|1[0-2])-(0[1-9]|12|3[01])。第一個分組 (0[1-9]|1[0-2]) 代表月的正則匹配,第二個分組 (0[1-9]|12|3[01]) 代表日的正則匹配。

Python 使用正規表示式

在 Python 中使用正規表示式很簡單,re 模組提供了正規表示式的支援。使用步驟一共三步:
將正規表示式字串轉換為 Pattern 的例項;使用 Pattern 例項去處理要匹配的字元,匹配結果是一個 Match 例項;利用 Match 例項去進行之後的操作。
在 Python 中我們常用的 re 的方法有六種,分別是: compilematchsearchfindallsplitsub

compile

compile 方法的作用是將正規表示式字串轉化為 Pattern 例項,它具有兩個引數 patternflagspattern 引數型別是 string 型別,接收的是正規表示式字串,flags 型別是 int 型別,接收的是匹配模式的編號,flags 引數是非必填項,預設值為 0 (忽略大小寫)。flags 匹配模式有 6 種:

匹配模式 說明
re.l 忽略大小寫
re.M 多行匹配模式
re.S 任意匹配模式
re.L 預定義字元匹配模式
re.U 限定字元匹配模式
import re
pattern = re.compile(r'\d')

match

match 的作用是利用 Pattern 例項,從字串左側開始匹配,如果匹配到就返回一個 Match 例項,如果沒有匹配到就返回 None。

import re
def getMatch(message):
    pattern = re.compile(r'(\d{4}[-年])(\d{2}[-月])(\d{2}日{0,1})')
    match = re.match(pattern, message)
    if match:
        print(match.groups())
        for item in match.groups():
            print(item)
    else:
        print("沒匹配上")
if __name__ == '__main__':
message = "2019年01月23日大會開始"
getMatch(message)
message = "會議於2019-01-23召開"
getMatch(message)

以上程式碼使用了 groups 方法,這個方法用來獲取匹配出來的字串組。為什麼第一段內容能匹配出來年月日,而第二段內容不能呢?這是因為 match 方法是從字串的起始位置匹配的。程式碼執行結果:

(‘2019年’, ‘01月’, ‘23日’)
2019年
01月
23日
沒匹配上

search

search 方法與 match 方法功能是一樣的,只不過 search 方法是對整個字串進行匹配。將前一小節程式碼中的 getMatch 方法進行改動,即可將第二段內容中的年月日匹配出來。

import re
def getMatch(message):
    pattern = re.compile(r'(\d{4}[-年])(\d{2}[-月])(\d{2}日{0,1})')
    match = re.search(pattern, message)
    if match:
        print(match.groups())
        for item in match.groups():
            print(item)
    else:
        print("沒匹配上")
if __name__ == '__main__':
message = "2019年01月23日大會開始"
getMatch(message)
message = "會議於2019-01-23召開"
getMatch(message)

執行結果:

2019年
01月
23日
(‘2019-‘, ‘01-‘, ‘23’)
2019-
01-
23

findall

findall 方法的作用是匹配整個字串,以列表的形式返回所有匹配結果。

import re
def getMatch(message):
    pattern = re.compile(r'\w+')
    match = re.findall(pattern, message)
    if match:
        print(match)
    else:
        print("沒匹配上")
if __name__ == '__main__':
message = "my name is 張三"
getMatch(message)
message = "張三 is me"
getMatch(message)

執行結果:

[‘my’, ‘name’, ‘is’, ‘張三’]
[‘張三’, ‘is’, ‘me’]

split

split 方法是利用指定的字元來分割字串。

import re
def getMatch(message):
    pattern = re.compile(r'-')
    match = re.split(pattern, message)
    if match:
        print(match)
    else:
        print("沒匹配上")
if __name__ == '__main__':
message = "2018-9-12"
getMatch(message)
message = "第一步-第二步-第三步-第四步-and more"
getMatch(message)

執行結果:

[‘2018’, ‘9’, ‘12’]
[‘第一步’, ‘第二步’, ‘第三步’, ‘第四步’, ‘and more’]

sub

sub 方法用來替換字串,它接受5個引數,其中常用的有三個:

  • pattern Pattern 例項
  • string 等待替換的字串
  • repl 表示替換的新字串或需要執行的替換方法
  • count 替換次數,預設為 0 表示全部替換
import re
def getMatch(match):
    return match.group(0).replace(r'年齡', 'age')
if __name__ == '__main__':
message = "your 年齡 ?"
pattern=re.compile(r'\w+')
print(re.sub(pattern,getMatch,message))

執行結果:

your age ?

本作品採用《CC 協議》,轉載必須註明作者和本文連結
不要試圖用百米衝刺的方法完成馬拉松比賽。

相關文章