天天看點

Python 3 進階程式設計 - 正規表達式

作者:逸劍聽潮

正規表達式在pthon程式設計中經常用到,如果寫過爬蟲程式的夥伴一定對正則不會陌生的。

正規表達式是一種特殊的字元序列,可幫助您使用模式中包含的專用文法比對或查找其他字元串或字元串集。正規表達式在 UNIX 世界中被廣泛使用。

Python 中 re 子產品提供了正規表達式的實作。如果在編譯或使用正規表達式時發生錯誤,則 re 子產品會引發異常 re.error。

正規表達式用到的比對符号的含義:

  • a, X, 9, < 等單個字元,普通字元隻是完全比對自己
  • . (點), 比對除換行符 '\n' 之外的任何單個字元
  • \w,比對“單詞”字元:字母或數字或下劃線 [a-zA-Z0-9_]
  • \W,比對任何非單詞字元
  • \b,詞與非詞的邊界
  • \s,比對單個空白字元——空格、換行符、回車符、制表符
  • \S,比對任何非空白字元
  • \t, \n, \r,制表符、換行符、回車
  • \d,比對十進制數字 [0-9]
  • ^,比對字元串的開頭
  • $,比對字元串的結尾
  • \,轉義特殊字元

編譯标志

編譯标志允許修改正規表達式工作方式的某些方面。 re 子產品中的标志有兩個名稱,一個是長名稱,如 IGNORECASE,另一個是短的單字母形式,如 I。

  • ASCII, A 使多個轉義符如 \w、\b、\s 和 \d 僅比對具有相應屬性的 ASCII 字元。
  • DOTALL, S 制作,比對任何字元,包括換行符
  • IGNORECASE, I 進行不區分大小寫的比對
  • LOCALE, L 進行語言環境感覺比對
  • MULTILINE, M 多行比對,影響^和$
  • VERBOSE, X (for ‘extended’) 啟用冗長的 RE,可以将其組織得更清晰、更易于了解

比對函數

match 函數嘗試将 RE 模式與帶有可選标志的字元串比對。比對函數的文法:

re.match(pattern, string, flags = 0)           

參數的含義:

  • pattern 要比對的正規表達式
  • string 字元串,将搜尋它以比對字元串開頭的模式
  • flags 是修飾符,使用按位或 (|) 指定不同的标志。

re.match 函數在成功時傳回一個比對對象,在失敗時傳回 None。使用比對對象的 group(num) 或 groups() 函數來擷取比對的表達式。

  • group(num = 0) 此方法傳回整個比對項(或特定子組編号)
  • groups() 此方法傳回元組中所有比對的子組(如果沒有則為空)

例如:

import re

line = "I want to watch a movie"

matchObj = re.match( r'(.*) to (.*?) .*', line, re.M|re.I)

if matchObj:
   print ("matchObj.group() : ", matchObj.group())
   print ("matchObj.group(1) : ", matchObj.group(1))
   print ("matchObj.group(2) : ", matchObj.group(2))
else:
   print ("No match!!")           

運作結果:

matchObj.group() :  I want to watch a movie
matchObj.group(1) :  I want
matchObj.group(2) :  watch           

搜尋功能

search 函數使用可選标志在字元串中搜尋 RE 模式的第一次出現。文法:

re.search(pattern, string, flags = 0)           

參數含義:

  • pattern 要比對的正規表達式
  • string 字元串,将搜尋它以比對字元串開頭的模式
  • flags 是修飾符,使用按位或 (|) 指定不同的标志。

re.search 函數在成功時傳回一個比對對象,在失敗時傳回一個比對對象。使用比對對象的 group(num) 或 groups() 函數來擷取比對的表達式。

  • group(num = 0) 此方法傳回整個比對項(或特定子組編号)
  • groups() 此方法傳回元組中所有比對的子組(如果沒有則為空)

例如:

import re

line = "I want to watch a movie"

searchObj  = re.search( r'(.*) to (.*?) .*', line, re.M|re.I)

if matchObj:
   print ("searchObj .group() : ", searchObj .group())
   print ("searchObj .group(1) : ", searchObj .group(1))
   print ("searchObj .group(2) : ", searchObj .group(2))
else:
   print ("No match!!")           

運作結果:

searchObj .group() :  I want to watch a movie
searchObj .group(1) :  I want
searchObj .group(2) :  watch           

比對 match與搜尋 search

Python 提供了兩種基于正規表達式的不同原始操作:比對僅檢查字元串開頭的比對項,而搜尋檢查字元串中任何位置的比對項。例如:

import re

line = "I want to watch a movie";

matchObj = re.match( r'movie', line, re.M|re.I)
if matchObj:
   print ("match --> matchObj.group() : ", matchObj.group())
else:
   print ("No match!!")

searchObj = re.search( r'movie', line, re.M|re.I)
if searchObj:
   print ("search --> searchObj.group() : ", searchObj.group())
else:
   print ("Nothing found!!")           

運作結果:

No match!!
search --> searchObj.group() :  movie           

搜尋和替換

使用正規表達式的最重要的 re 方法之一 sub 實作搜尋替換。文法:

re.sub(pattern, repl, string, max=0)           

此方法用 repl 替換字元串中所有比對給定 RE 正規表達式的地方,替換所有比對項,除非達到了 max 的限制數量。此方法傳回修改後的字元串。

import re

code = "100-1001-210 # This is a code"

num = re.sub(r'#.*#39;, "", phone)   # 去除注釋部分
print ("Code Num : ", num)

num = re.sub(r'\D', "", phone)     #去除除了數字之外的所有字元
print ("Code Num : ", num)
num = re.sub(r'\D', " ", phone)      #數字之外的所有字元用空格代替
print ("Code Num : ", num)           

運作結果:

Code Num :  100-1001-210 
Code Num :  1001001210
Code Num :  100 1001 210               

正規表達式修飾符:可選标志

則表達式文字可能包括一個可選的修飾符來控制比對的各個方面。修飾符被指定為可選标志。可以使用異或 (|) 使用多個修飾符,如前面例子中的re.M|re.I。這些修飾符的含義如下:

  • re.I 執行不區分大小寫的比對。
  • re.L 根據目前語言環境解釋單詞。這種解釋會影響字母組(\w 和 \W)以及單詞邊界行為(\b 和 \B)。
  • re.M 使 $ 比對行的結尾(不僅僅是字元串的結尾)并使 ^ 比對任何行的開頭(而不僅僅是字元串的開頭)。
  • re.S 使.(點)比對任何字元,包括換行符。
  • re.U 根據 Unicode 字元集解釋字母。此标志影響 \w、\W、\b、\B 的行為。
  • re.X 忽略空格(集合 [] 内或被反斜杠轉義時除外)并将未轉義的 # 視為注釋标記。

正規表達式模式

除控制字元 (+ ? . * ^ $ ( ) [ ] { } | \) 外,所有字元都隻能比對自己。可以通過在控制字元前加上反斜杠來轉義控制字元。

字元比對,示例及說明:

  • python 比對整個“python”字元串
  • [Pp]ython 比對“Python”或“python”
  • rub[ye] 比對“ruby”或“rube”
  • [aeiou] 比對任何一個小寫元音字母
  • [0-9] 比對任意數字;與 [0123456789] 相同
  • [a-z] 比對任何小寫 ASCII 字母
  • [A-Z] 比對任何大寫 ASCII 字母
  • [a-z -Z0-9] 比對任何大寫小寫字母和數字
  • [^aeiou] 比對小寫元音以外的任何内容
  • [^0-9] 比對數字以外的任何内容

例如:

import re
 
r = r"Python"
str = "It's easy to learn python. Python is simple."
m = re.search(r, str)
print(m)
 
r1 = r"[Pp]ython"   #比對 python或 Python
m1 = re.search(r1, str)
print(m1)

r2 = r"[a-z]"    #比對一個小寫字母
m2 = re.search(r2, str)
print(m2)           

運作結果

<re.Match object; span=(27, 33), match='Python'>
<re.Match object; span=(19, 25), match='python'>
<re.Match object; span=(1, 2), match='t'>           

特殊字元比對

  • . (點) 比對除換行符以外的任何字元
  • \d 比對數字:[0-9]
  • \D 比對一個非數字:[^0-9]
  • \s 比對空白字元:[ \t\r\n\f]
  • \S 比對非空白:[^ \t\r\n\f]
  • \w 比對單個單詞字元:[A-Za-z0-9_]
  • \W 比對一個非單詞字元:[^A-Za-z0-9_]

例如:

import re
 
r = r"\d"   #比對一個數字
str = "Python 3.0 was released in 2008."
m = re.search(r, str)
print(m)

r1 = r"\w"   #比對一個大寫,小寫或數字
m1 = re.search(r1, str)
print(m1)           

運作結果:

<re.Match object; span=(7, 8), match='3'>
<re.Match object; span=(0, 1), match='P'>           

重複比對

  • ? 重複前面一個比對字元零次或者一次。例如 ruby? 比對“rub”或“ruby”
  • * 重複前面一個比對字元零次或者多次。例如 ruby* 比對“rub”或“rubyyy”可加多個y
  • + 重複前面一個比對字元一次或者多次。例如 ruby* 比對“ruby”或“rubyyy”可加多個y
  • {n} 比對 n 個字元。例如 ruby{3} 比對 “ruby”後可加任意3個字元,“rubyabc”
  • {m,n} 比對 m 到 n 個字元。例如 ruby* 比對
  • {n,} 比對 n 個或多個字元。例如 ruby* 比對

例如:

import re
 
r = r"Py?"   #比對前面字元零次或者一次
m = re.search(r, "Python")  #比對前面字元一次
print(m)
m = re.search(r, "P0ython")  #比對前面字元零次
print(m)

r=r"Py+"  #比對字元一次或者多次
m=re.search(r,"Python")  #比對字元一次
print(m)
m=re.search(r,"Pyyython")  #比對字元零次或者多次
print(m)
 
r=r"Py*"   #比對字元零次或者多次
m=re.search(r,"P0ython")  #比對字元一次
print(m)
m=re.search(r,"Pyyython")  #比對字元零次或者多次
print(m)

r=r"Py{4}"   #比對前面字元4次
m=re.search(r,"Pyyyyyyython")  #比對字元4次
print(m)

r=r"Py{4,}"   #比對前面字元4次或更多次
m=re.search(r,"Pyyyyyyyyyyyyyyyython")  #比對字元多次
print(m)           

運作結果

<re.Match object; span=(0, 2), match='Py'>
<re.Match object; span=(0, 1), match='P'>
<re.Match object; span=(0, 2), match='Py'>
<re.Match object; span=(0, 4), match='Pyyy'>
<re.Match object; span=(0, 1), match='P'>
<re.Match object; span=(0, 4), match='Pyyy'>
<re.Match object; span=(0, 5), match='Pyyyy'>
<re.Match object; span=(0, 17), match='Pyyyyyyyyyyyyyyyy'>           

非貪婪重複比對

比對最少的重複次數。

防止過度比對

貪婪比對:

+ 是貪婪比對,比對次數隻接受一次或者多次,例如 pythonn+ 中的紅色 n 如果出現在 python 後面出現一次 n 的時候,立刻傳回比對成功的值,同時由于是貪婪比對,在 python 後面出現兩或多個 n 的時候,也會傳回比對成功的值。當 n 為零次的時候,即為 python 時,是不會有傳回值的!

import re

r=r"pythonn+"   #貪婪比對
m=re.search(r,"pythonnnnn")   
print(m)
#運作結果
<re.Match object; span=(0, 10), match='pythonnnnn'>           

* 是貪婪比對,比對次數接受零次一次或者多次,此時 pythonn+ 中的紅色 n 如果出現在 python 後面出現零次的時候,立刻傳回比對成功的值,同時由于是貪心比對,在 python 後面出現一個或多個 n時,也會傳回比對成功的值。

import re

r=r"pythonn*"   #貪婪比對
m=re.search(r,"pythonnnnn")   
print(m)
#運作結果
<re.Match object; span=(0, 10), match='pythonnnnn'>           

?是非貪婪比對,比對次數隻接受零次或者一次,同時如果零次滿足,則一次比對不再繼續。例如 pythonn+ 中的紅色 n 如果出現在 python 後面出現零次的時候,立刻傳回比對成功的值,同時由于是非貪心比對,在 python 後面出現一個或多個 n 的時候,也不會比對。

import re

r=r"pythonn?"   #非貪婪比對
m=re.search(r,"python")   
print(m)

m=re.search(r,"pythonnnnn")   
print(m)
#運作結果
<re.Match object; span=(0, 6), match='python'>
<re.Match object; span=(0, 7), match='pythonn'>           

控制貪婪比對

在 * 和 + 後面加上 ?就是把貪婪比對更改為非貪婪比對:

* 号後面加 ?變成非貪婪比對,比對 0 次成功就會退出比對。

+ 号後面加上 ?變成非貪婪比對,比對1次成功就會推出比對。

例如:

import re

r=r"pythonn+?"   #非貪婪比對,1次比對
m=re.search(r,"pythonnnnnnnnn")   
print(m)

r=r"pythonn*?"   #非貪婪比對,0次比對
m=re.search(r,"pythonnnn")   
print(m)
#運作結果
<re.Match object; span=(0, 7), match='pythonn'>
<re.Match object; span=(0, 6), match='python'>           

搞懂了貪婪比對,非貪婪比對,什一次比對,多次比對之後,就可以靈活運用正則比對了。

用括号分組

\D\d+ 無分組:+ 重複 \d

(\D\d)+ 分組:+ 重複 \D\d 對

例如:([Pp]ython(,)?)+ 比對“Python”、“Python, python, pythonnnnnn”等。

import re

r = r"([Pp]ython(,)?)+"   #分組比對
m = re.search(r,"Python")   
print(m)

r = r"([Pp]ython(,)?)+"   #分組比對
m = re.search(r,"pythonnnnnnnn Python  python")   
print(m)
#運作結果
<re.Match object; span=(0, 6), match='Python'>
<re.Match object; span=(0, 6), match='python'>           

反向引用(\)

再次比對先前比對的分組。例如

([Pp])ython&\1ails 比對 python&pails 或 Python&Pails

(['"])[^\1]*\1 單引号或雙引号字元串。 \1 比對第一組比對的任何内容。 \2 比對第二組比對的任何内容,等等。

備擇方案(|)

python|perl 比對“python”或“perl”

rub(y|le) 比對"ruby" 或"ruble"

Python(!+|\?) “Python”後跟一個或多個!還是一個?

錨點

指定比對位置.

^ 比對字元串的開頭。

$ 比對字元串的末尾。

\A 比對字元串開始

\Z 比對字元串結束,如果是存在換行,隻比對到換行前的結束字元串。

\z 比對字元串結束

\b 比對一個單詞邊界,也就是指單詞和空格間的位置。例如, 'er\b' 可以比對"never" 中的 'er',但不能比對 "verb" 中的 'er'。

\B 比對非單詞邊界。'er\B' 能比對 "verb" 中的 'er',但不能比對 "never" 中的 'er'。

(?= re) 前向肯定界定符。如果所含正規表達式,以 ... 表示,在目前位置成功比對時成功,否則失敗。Python(?=!) 如果後跟感歎号,則比對“Python”

(?! re) 前向否定界定符。與肯定界定符相反;當所含表達式不能在字元串目前位置比對時成功。Python(?!!) 如果後面沒有感歎号,則比對“Python”。

帶括号的特殊文法

R(?#comment) 比對“R”。剩下的都是注釋

R(?i)uby 比對“uby”時不區分大小寫

R(?i:uby) 比對“uby”時不區分大小寫

rub(?:y|le)) 僅分組而不建立 \1 反向引用

複雜例子,判斷電話号碼和郵箱位址:

import re
 
americaPhoneRegex = re.compile(r'''(
    (\d{3}|\(\d{3}\))?   # area code
    (\s|-|\.)?           # separator
    (\d{8})              # first 8 digits
    (\s|-|\.)            # separator
    (\d{4})              # last 4 digits
    (\s*(ext|x|ext.)\s*(\d{2,5}))?   # extension
    )''', re.VERBOSE)    
 
emailPhoneRegex   = re.compile(r'''(
        [a-zA-Z0-9._%+-]+      # username
        @                      # @ symbol
        [a-zA-Z0-9.-]+         # domain name
        (\.[a-zA-Z]{2,4})      # dot-something
        )''', re.VERBOSE)
 
 
phone = '029-12341234-1234'
email = "[email protected]"
matches = []
for groups in americaPhoneRegex.findall(phone):
    phoneNum = '-'.join([groups[1], groups[3], groups[5]])
    if groups[8] != '':
        phoneNum += ' x' + groups[8]
    matches.append(phoneNum)
 
for groups in emailPhoneRegex.findall(email):
    matches.append(groups[0])
 
if len(matches) > 0:
    print('\n'.join(matches))
else:
    print('No phone numbers or email addresses found.')