Python之正則表達式(re模塊)

本節內容


  1. re模塊介紹
  2. 使用re模塊的步驟
  3. re模塊簡單應用示例
  4. 關於匹配對象的說明
  5. 說說正則表達式字符串前的r前綴
  6. re模塊綜合應用實例
  7. 參考文檔

提示: 因爲該站對MARKDOWN的表格支持的不是很好,因此本文中的表格均以圖片的形式提供,你們若是看着比較模糊,能夠放大來看或下載圖片在本地查看。html

正則表達式(Regluar Expressions)又稱規則表達式,在代碼中常簡寫爲REs,regexes或regexp(regex patterns)。它本質上是一個小巧的、高度專用的編程語言。 經過正則表達式能夠對指定的文本實現
匹配測試、內容查找、內容替換、字符串分割 等功能。正則表達式的語法不是本節要講的內容(關於正則表達式的詳細介紹請參考另外一篇博文《正則表達式總結》),本節主要介紹的是Python中是如何使用re模塊來完成正則表達式的相關操做的。python

1、re模塊介紹


Python中的re模塊提供了一個正則表達式引擎接口,它容許咱們將正則表達式編譯成模式對象,而後經過這些模式對象執行模式匹配搜索和字符串分割、子串替換等操做。re模塊爲這些操做分別提供了模塊級別的函數以及相關類的封裝。正則表達式

1. re模塊提供的類

Python中的re模塊中最重要的兩個類:編程

描述
Regular Expression Objects 正則表達式對象,用於執行正則表達式相關操做的實體
Match Objects 正則表達式匹配對象,用於存放正則表達式匹配的結果並提供用於獲取相關匹配結果的方法

正則表達式對象中的方法和屬性

經過re模塊的compile()函數編譯獲得的正則表達式對象(下面用regex表示)支持以下方法:socket

參數說明:
  • string: 要匹配或處理的字符串
  • pos: 可選參數,表示從string字符串的哪一個位置開始,至關於先對字符串作切片處理string[pos:]
  • endpos: 可選參數,表示到string字符串的哪一個位置結束(不包含該位置)
  • maxsplit: regex.split()方法的可選參數,表示最大切割次數;默認值爲0,表示能切割多少次就儘量多的切割多少次
  • count: regex.sub()和regex.subn()方法的可選參數,表示最大替換次數;默認爲0,表示能替換多少次就儘量多的替換多少次
  • repl: sub和subn函數中的repl表示replacement,用於指定將匹配到的子串替換成什麼內容,須要說明的是該參數的值能夠是一個字符串,也能夠是一個函數

說明: 若是指定了pos和endpos參數,就至關於在進行正則處理以前先對字符串作切片操做 string[pos, endpos],如rx.search(string, 5, 50)就等價於rx.search(string[5:50]),也等價於rx.search(string[:50], 5);若是endpos比pos的值小,則不會找到任何匹配。編程語言

匹配對象中的方法和屬性

調用正則表達式對象的regex.match()、regex.fullmatch()和regex.search()獲得的結果就是一個匹配對象,匹配對象支持如下方法和屬性:函數

參數說明:
  • template: m.expand()方法中的template參數是一個模板字符串,這個字符串中可使用分組對應的的數值索引進行後向引用(如:\1,\2)或命名後向引用(如\g<1>,\g<NAME>)來表示某個分組的佔位符;m.expand()方法的執行過程實際上就是經過sub()方法把template字符串中的這些分組佔位符用當前匹配對象中的數據進行替換。
  • default: m.groups()與m.groupdict()方法中的default都是爲未匹配成功的捕獲組提供默認匹配值的。
  • group: m.group()、m.start()、m.end()和m.span()方法中的group參數都表示要選擇的分組索引值,1表示第一個分組,2表示第二個分組,依次類推,group參數的默認值是0,表示整個正則表達式所匹配的內容。

2. re模塊提供的函數

re模塊提供瞭如下幾個模塊級別的函數工具

將pattern參數只能是字符串;測試

  • string: 須要用正則表達式來匹配的字符串對象
  • flags: 一個標誌位,它會影響正則表達式對象的匹配行爲,可取值下面會介紹;可是有一點須要說明的是,只有當pattern參數是字符串時才能指定這個flags參數,不然會報錯;若是pattren參數是一個正則表達式對象,則flags參數須要在調用re.compile()函數時指定。
  • repl: sub和subn函數中的repl表示replacement,用於指定將匹配到的子串替換成什麼內容,須要說明的是該參數的值能夠是一個字符串,也能夠是一個函數
  • count: sub和subn函數中的count表示最多可替換次數
  • maxsplit: split函數中的maxsplit蠶食表示最大分隔次數

說明: 經過對比會發現,上面這些re模塊級別的函數除了re.compile、re.purge和re.escape這幾個函數外,其它函數名都與正則表達式對象支持的方法同名。實際上re模塊的這些函數都是對正則表達式對象相應方法的封裝而已,功能是相同的。只是少了對pos和endpos參數的支持,可是咱們能夠手動經過字符串切片的方式來達到相應的需求。我的認爲,咱們應該儘量的使用模塊級別的函數,這樣能夠加強代碼的兼容性。ui

3. 標誌位flags

由上面的描述可知,flags參數在上面這些模塊函數中是要個可選參數,re模塊中預約了該參數可取的值:

說明: 這些flag能夠單獨使用,也能夠經過邏輯或操做符'|'進行拼接來聯合使用。

2、使用re模塊的步驟


咱們有必要對re模塊中所包含的類及其工做流程進行一下簡單的、總體性的說明,這講有利於咱們對下面內容的理解。

1. 使用re模塊進行正則匹配操做的步驟

  • 1)編寫表示正則表達式規則的Python字符串str;
  • 2)經過re.compile()函數編譯該Python字符串得到一個正則表達式對象(Pattern Object)p;
  • 3)經過正則表達式對象的p.match()或p.fullmatch()函數獲取匹配結果--匹配對象(Match Object)m;
  • 4)經過判斷匹配對象m是否爲空可知是否匹配成功,也能夠經過匹配對象m提供的方法獲取匹配內容。

2. 使用re模塊進行內容查找、替換和字符串分隔操做的步驟

  • 1)編寫表示正則表達式規則的Python字符串str;
  • 2)經過re.compile()函數編譯該Python字符串得到一個正則表達式對象(Pattern Object)p;
  • 3)經過正則表達式對象的p.search()或p.findall()或p.finditer()或p.sub()或p.subn()或p.split()函數完內容查找、替換和字符串分隔操做並獲取相應的操做結果;

總結: 關於正則表達式的語法和編寫示例請參考《正則表達式總結》)。根據上面的描述可知,將一個表示正則表達式的Python字符串編譯成一個正則表達式對象是使用正則表達式完成相應功能的首要步驟,re模塊中用於完成正則表達式編譯功能的函數爲re.compile()。

3、re模塊簡單應用示例


在上面的內容中,咱們已經列出了re模塊所提供的類,以及這些類的對象所支持的函數和屬性,還有re模塊所提供的模塊級別的函數。這裏咱們要討論的是怎樣合理的使用這些方法和函數,換句話說就是在什麼狀況下使用這些方法和函數,以及如何使用的問題。咱們以前說過,正則表達式主要能夠用來提供如下幾個功能:

  • 匹配測試
  • 子串/內容查找
  • 子串/內容替換
  • 字符串分割

下面咱們就分別經過對這幾個功能的實例實現對上面這些函數和方法以及參數和屬性的使用作下說明和具體的解釋。

1. 匹配測試

匹配測試,意思是經過特定的正則表達式對一個指定的字符串進行匹配來判斷該字符串是否符合這個正則表達式所要求的格式。常見的使用場景舉例:

  • 查看某個字符串(一般來自用戶輸入)是不是一個郵箱或電話號碼
  • 用戶註冊時填寫的用戶名和密碼是否符合指定需求
使用的函數或方法

經過re模塊執行匹配測試時可使用的函數或方法是:

  • re模塊級別的match()和fullmatch()函數
  • 正則表達式對象的match()和fullmatch()方法
實例1

前面提到過,match()函數或方法只是匹配字符串的開始位置,而fullmatch匹配的是整個字符串;fullmatch()函數或方法就至關於給match()函數或方法的pattern或string參數加上行首邊界元字符'^'和行尾邊界元字符'$',下面來看個例子:

import re

# 定義一個函數來對匹配結果進行展現 
def display_match_obj(match_obj):
    if match_obj is None:
        print('Regex Match Fail!')
    else:
        print('Regex Match Success!', match_obj)

if __name__ == '__main__':
    p = re.compile(r'[a-z]+')
    display_match_obj(p.match('hello'))
    display_match_obj(p.match('hello123'))
    display_match_obj(p.fullmatch('hello'))
    display_match_obj(p.fullmatch('hello123'))
    
    display_match_obj(p.match('123hello'))
    display_match_obj(p.match('123hello', 3))

輸出結果:

Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'>
Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'>
Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'>
Regex Match Fail!
Regex Match Fail!
Regex Match Success! <_sre.SRE_Match object; span=(3, 8), match='hello'>

分析:

  • '[a-z]+'能與'hello'和'hello123'的開頭部分匹配
  • '[a-z]+'能與'hello'徹底匹配
  • '[a-z]+'不能與'hello123'徹底匹配
  • '[a-z]+'不能與'123hello'的開頭部分匹配
  • '[a-z]+'能與'123hello'的切片'123hello'[3:]的開頭部分匹配
實例2

上面使用的是正則表達式對象的match()和fullmatch()方法,咱們也能夠經過re提供的模塊級別的函數來實現:

if __name__ == '__main__':
    p = re.compile(r'[a-z]+')
    display_match_obj(re.match(p, 'hello'))
    display_match_obj(re.match(p, 'hello123'))
    display_match_obj(re.fullmatch(p, 'hello'))
    display_match_obj(re.fullmatch(p, 'hello123'))
    
    display_match_obj(re.match(p, '123hello'))
    display_match_obj(re.match(p, '123hello'[3:]))  # 惟一不一樣的是這裏

輸出結果跟上面是同樣的

re模塊的match()和fullmatch()函數不支持pos和endpos參數,因此只能經過字符串切片先對字符串進行切割。

實例3

再來看個對比:

if __name__ == '__main__':
    display_match_obj(re.match(r'[a-z]+', 'hello123'))
    display_match_obj(re.match(r'[a-z]+$', 'hello123'))
    display_match_obj(re.match(r'^[a-z]+$', 'hello123'))

輸出結果:

Regex Match Success! <_sre.SRE_Match object; span=(0, 5), match='hello'>
Regex Match Fail!
Regex Match Fail!

分析:

  • re.match()和re.fullmatch()中的pattern參數能夠是正則表達式對象,也能夠是Python字符串
  • re.match()函數中的正則表達式參數加上邊界元字符'^'和'$'就至關於re.fullmatch()了
  • 當匹配過程是從字符串的第一個字符開始匹配時re.match(r'^[a-z]+$', 'hello123') 與 re.match(r'[a-z]+$', 'hello123')效果是同樣的,由於re.match()原本就是從字符串開頭開始匹配的;可是,若是匹配過程不是從字符串的第一個字符開始匹配時,它們是有區別的,具體請看下面這個例子。
實例4
if __name__ == '__main__':
    p1 = re.compile(r'[a-z]+$')
    p2 = re.compile(r'^[a-z]+$')
    display_match_obj(p1.match('123hello', 3))
    display_match_obj(p2.match('123hello', 3))

輸出結果:

Regex Match Success! <_sre.SRE_Match object; span=(3, 8), match='hello'>
Regex Match Fail!

這個很好理解,由於元字符'^'匹配的是表示字符串開始位置的特殊字符,而不是字符串內容的第一個字符。match()匹配的是字符串內容的第一個字符,所以即便是在MULTILINE模式,re.match()也將只匹配字符串的開始位置,而不是該字符串每一行的行首。

2. 內容查找

內容查找,意思是經過特定的正則表達式對一個指定的字符串的內容進行掃描來判斷該字符串中是否包含與這個正則表達式相匹配的內容。

使用的函數或方法

經過re模塊執行匹配測試時可使用的函數或方法是:

  • re模塊級別的search()、findall()和 finditer()函數
  • 正則表達式對象的search()、findall()和finditer()方法
實例1:search() vs match()

這個例子中,咱們來看下search()函數的使用,以及它與match()函數的對比。
Python提供了兩個不一樣的基於正則表達式的簡單操做:

  • re.match(): 該函數僅是在字符串的開始位置進行匹配檢測
  • re.search(): 該函數會在字符串的任意位置進行匹配檢測
import re

string = 'abcdef'
print(re.match(r'c', string))
print(re.search(r'c', string))

輸出結果:

None
<_sre.SRE_Match object; span=(2, 3), match='c'>

這個比較簡單,不作過多解析。

咱們應該可以想到,當正則表達式以'^'開頭時,search()也會從字符串的開頭進行匹配:

import re

string = 'abcdef'
print(re.match(r'c', string))
print(re.search(r'^c', string))
print(re.search(r'^a', string))

輸出結果:

None
None
<_sre.SRE_Match object; span=(0, 1), match='a'>

這裏再重複一下上面提到過的內容,就是元字符'^'與match()的從字符串開始位置開始匹配並非徹底等價的。由於元字符'^'匹配的是表示字符串開始位置的特殊字符,而不是字符串內容的第一個字符。match()匹配的是字符串內容的第一個字符,所以即便是在MULTILINE模式,re.match()也將只匹配字符串的開始位置,而不是該字符串每一行的行首;相關search('^...')卻能夠匹配每一行的行首。

下面來看個例子:

string = '''
A
B
X
'''

print(re.match(r'X', string, re.MULTILINE))
print(re.search(r'^X', string, re.MULTILINE))

輸出結果:

None
<_sre.SRE_Match object; span=(5, 6), match='X'>
實例2:findall()與finditer()

findall()與finditer()也是用來查找一個字符串中與正則表達式相匹配的內容,可是從名字上就能看出來,findall()與finditer()會講這個字符串中全部與正則表達式匹配的內容都找出來,而search()僅僅是找到第一個匹配的內容。另外findall()返回的是全部匹配到的子串所組成的列表,而finditer()返回的是一個迭代器對象,該迭代器對象會將每一次匹配到的結果都做爲一個匹配對象返回。下面來看一個例子:

嘗試找出一個字符串中的全部副詞(英語的副詞一般都是以字母‘ly’結尾):

import re

text = "He was carefully disguised but captured quickly by police."
print(re.findall(r"\w+ly", text))

輸出結果:

['carefully', 'quickly']

若是咱們想要獲取關於全部匹配內容的更多信息,而不只僅是文本信息的話,就可使用finditer()函數。finditer()能夠提供與各個匹配內容相對應的匹配對象,而後咱們就能夠經過這個匹配對象的方法和屬性來獲取咱們想要的信息。

咱們來嘗試獲取一個字符串中全部副詞以及它們各自在字符串中的切片位置:

import re

text = "He was carefully disguised but captured quickly by police."

for m in re.finditer(r"\w+ly", text):
    print("%02d-%02d: %s" % (m.start(), m.end(), m.group()))

輸出結果:

07-16: carefully
40-47: quickly

3. 內容替換

傳統的字符串操做只能替換明確指定的子串,而使用正則表達式默認是對一個字符串中全部與正則表達式相匹配的內容進行替換,也能夠指定替換次數。

可以使用的函數或方法
  • re模塊的sub()和subn()函數
  • 正則表達式對象的sub()和subn()方法

sub函數返回的是被替換後的字符串,若是字符串中沒有與正則表達式相匹配的內容,則返回原始字符串;subn函數除了返回被替換後的字符串,還會返回一個替換次數,它們是以元組的形式返回的。下面來看個例子:將一個字符串中的全部的'-'字符刪除

import re

text = 'pro----gram-files'
print(re.sub(r'-+', '', text))
print(re.subn(r'-+', '', text))

輸出結果:

programfiles
('programfiles', 2)

說明: 被替換的內容(repl參數)能夠是一個字符串,還能夠是一個函數名。該函數會在每一次匹配時被調用,且該函數接收的惟一的參數是當次匹配相對應的匹配對象,經過這個函數咱們能夠來作一些邏輯更加複雜的替換操做。

好比,上面那個例子中,若是咱們想要獲得'program files'這個結果,咱們就須要把多個連續的'-'和單個'-'分別替換爲 空字符串 和 一個空白字符:

def dashrepl(match_obj):
    if match_obj.group() == '-':
        return ' '
    else:
        return ''

if __name__ == '__main__':
    text = 'pro----gram-files'
    print(re.sub(r'-+', dashrepl, text))
    print(re.subn(r'-+', dashrepl, text))

輸出結果:

program files
('program files', 2)

說明: 當被替換的內容(repl參數)是一個字符串時可使用'\1'、'\g<NAME>'來引用正則表達式中的捕獲組所匹配到的內容,可是須要注意的是這個字符串必須帶上r前綴。

下面來看個例子:把一個函數名前面加上'py_'前綴

p = r'def\s+([A-Za-z_]\w*)\s*\((?P<param>.*)\)'
repl = r'def py_\1(\g<param>)'
text = 'def myfunc(*args, **kwargs):'
print(re.sub(p, repl, text))

輸出結果:

def py_myfunc(*args, **kwargs):

4. 字符串分割

經過正則表達式對字符串進行分割的過程是:掃描整個字符串,查找與正則表達式匹配的內容,而後以該內容做爲分割符對字符串進行分割,最終返回被分割後的子串列表。這對於將文本數據轉換爲Python易於讀取和修改的結構化數據很是有用。

可使用的函數或方法
  • re模塊的split()函數
  • 正則表達式對象的split()方法

咱們能夠經過maxsplit參數來限制最大切割次數。

實例1:簡單示例
print(re.split(r'\W+', 'Words, words, words.'))
print(re.split(r'\W+', 'Words, words, words.', 1))
print(re.split(r'[a-f]+', '0a3B9', flags=re.IGNORECASE))

輸出結果:

['Words', 'words', 'words', '']
['Words', 'words, words.']
['0', '3', '9']

分析:

  • 第一行代碼中,一共分割了3次(分隔符分別爲:兩個', '和一個'.'),所以返回的列表中有4個元素;
  • 第二行代碼中,限制了最大分割次數爲1,所以返回的列表中只有2個元素;
  • 第三行代碼中,指定分隔符爲一個或多個連續的小寫字字母,可是指定的flag爲忽略大小寫,所以大寫字母也能夠做爲分隔符使用;那麼從小寫字母'a'和大寫字母'B'分別進行切割,因此返回的列表中有3個元素。
實例2:捕獲組與匹配空字符串的正則表達式
  • 若是用於做爲分割符的正則表達式包含捕獲組,那麼該捕獲組所匹配的內容也會做爲一個結果元素被返回;
  • 從Python 3.5開始,若是做爲分隔符的正則表達式能夠匹配一個空字符串,將會引起一個warning;
  • 從Python 3.5開始,若是做爲分隔符的正則表達式只能匹配一個空字符串,那麼它將會被拒絕使用並拋出異常。
print(re.split(r'(\W+)', 'Words, words, words.'))
print(re.split(r'x*', 'abcxde'))
print(re.split(r'^$', '\nfoo\nbar\n', re.M))

輸出結果:

['Words', ', ', 'words', ', ', 'words', '.', '']

C:\Python35\lib\re.py:203: FutureWarning: split() requires a non-empty pattern match.
  return _compile(pattern, flags).split(string, maxsplit)
['abc', 'de']

Traceback (most recent call last):
  File "C:/Users/wader/PycharmProjects/PythonPro/regex2.py", line 68, in <module>
    print(re.split(r'^$', '\nfoo\nbar\n', re.M))
  File "C:\Python35\lib\re.py", line 203, in split
    return _compile(pattern, flags).split(string, maxsplit)
ValueError: split() requires a non-empty pattern match.
實例3:實現一個電話簿

咱們如今要使用Python正則表達式的字符串分割功能實現一個電話簿,具體流程是:從一個給定的文本中讀取非空的行,而後把這些非空行進行切割獲得相關信息。

text = """Ross McFluff: 834.345.1254 155 Elm Street

Ronald Heathmore: 892.345.3428 436 Finley Avenue
Frank Burger: 925.541.7625 662 South Dogwood Way


Heather Albrecht: 548.326.4584 919 Park Place"""

entries = re.split(r'\n+', text)
phonebook = [re.split(r':?\s+', entry, 3) for entry in entries]
print(phonebook)

輸出結果:

[
    ['Ross', 'McFluff', '834.345.1254', '155 Elm Street'], 
    ['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
    ['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
    ['Heather', 'Albrecht', '548.326.4584', '919 Park Place']
]

說明:實際輸出中是沒有這樣的縮進格式的,這裏只是爲了方便你們查看。

分析:

  • 上面這個例子整體上有兩個分割過程,第一個分割是經過換行符爲分割符獲得全部的非空行列表,第二個分割是對獲得的每一行文本進行切割,獲得每個聯繫人的相關信息;
  • 每一行的內容分別爲:firstname, lastname, tel, addr;
  • 因爲lastname與tel之間包含一個冒號,而其餘內容之間只是包含空白字符,因此用於做爲分隔符的正則表達式應該是':?\s+';
  • 因爲最後的那個addr是由多個段組成,且這些段之間也有空白字符,此時能夠利用maxsplit參數限定最大分割次數,從而使得最後的3個字段做爲一個addr總體返回。

4、關於匹配對象的說明


當咱們經過re.match或re.search函數獲得一個匹配對象m後,能夠經過if m is None來判斷是否匹配成功。在匹配成功的條件下,咱們可能還想要獲取匹配的值。Python中的匹配對象主要是以「組」的形式來獲取匹配內容的,下面咱們來看下具體操做:咱們如今要經過一個正則表達式獲取一個字符串中的'姓名'、'年齡'和'手機號碼'

import re

p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL)

string = '''
My name is Tom,
I am 16 years old,
My tel is 13972773480.
'''
m = re.match(p, string)
# 或
# m = p.match(string)

if m is None:
    print('Regex match fail.')
else:
    result = '''
    name: %s
    age:  %s
    tel:  %s
    '''
    print(result % (m.group(1), m.group(2), m.group('tel')))

輸出結果:

name: Tom
age:  16
tel:  13972773480

分析:

  • 因爲要匹配的字符串中包括換行符,爲了讓元字符'.'可以匹配換行符,因此編譯正則表達式須要指定re.DOTALL這個flag;
  • 調用匹配對象的方法或屬性前須要判斷匹配是否成功,若是匹配失敗,獲得的匹配對象將是None,其方法和屬性調用會報錯;
  • 對於"非命名捕獲組"只能經過分組索引數字來獲取其匹配到的內容,如m.group(1);而對於命名捕獲組既能夠經過分組索引數字來獲取其匹配到的內容,如m.group(2),也能夠經過分組名稱來獲取其匹配到的內容,如m.group('tel')。
使用match.expand(template)方法

咱們在上面介紹匹配對象所提供的方法時對expand方法進行過說明:

match.expand(template)方法可用於經過獲得的匹配對象來構造並返回一個新的字符串,template是一個字符串,用於指定新字符串的格式;從Python 3.5開始,未匹配到的分組將會替換爲一個空字符串

這裏咱們用它來實現上面這個例子的輸出效果,你們體會下它的功能:

import re

p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL)

string = '''
My name is Tom,
I am 16 years old,
My tel is 13972773480.
'''
# m = re.match(p, string)
# 或
m = p.match(string)

if m is None:
    print('Regex match fail.')
else:
    template_str = '''
    name: \g<1>
    age:  \g<2>
    tel:  \g<tel>
    '''
    print(m.expand(template_str))

輸出結果,跟上面是同樣的。

打印匹配對象中包含的數據

這裏,咱們主要是想經過對匹配對象各個方法和屬性的調用,讓你們更深入的理解經過這些方法和屬性咱們能夠獲得什麼。

p = re.compile(r'.*name\s+is\s+(\w+).*am\s+(?P<age>\d{1,3})\s+years.*tel\s+is\s+(?P<tel>\d{11}).*', re.DOTALL)

string = '''
My name is Tom,
I am 16 years old,
My tel is 13972773480.
'''

# m = re.match(p, string)
# 或
m = p.match(string)

if m is None:
    print('Regex Match Fail!')
else:
    print('match_obj.group(): ', '匹配到的全部內容: ', m.group())
    print('match_obj.group(0): ', '同上: ', m.group(0))
    print('match_obj.group(1): ', '第一個捕獲組匹配到的內容: ', m.group(1))
    print('match_obj.group(2): ', '第二個命名捕獲組匹配到的內容: ', m.group(2))
    print('match_obj.group("tel"): ', '第三個命名捕獲組匹配到的內容: ', m.group('tel'))
    print('match_obj.groups(): ', '全部捕獲組匹配到的內容組成的元組對象: ', m.groups())
    print('match_obj.groupdict(): ', '全部命名捕獲組匹配到的內容組成的字典對象: ', m.groupdict())
    print('match_obj: ', '直接打印匹配對象: ', m)
    print('match_obj.string: ', '傳遞給re.match函數的字符串參數: ', m.string)
    print('match_obj.re: ', '傳遞給re.match函數的正則表達式對象: ', m.re)
    print('match_obj.pos, match_obj.endpos: ', '傳遞個match方法的pos和endpos參數: ', m.pos, m.endpos)
    print('match_obj.pos, match_obj.start(1), match_obj.end(1): ', '第一個捕獲組匹配的內容在字符串中的切片位置: ', m.start(1), m.end(1))
    print('match_obj.pos, match_obj.span(1): ', '第一個捕獲組匹配的內容在字符串中的切片位置: ', m.span(1))
    print('match_obj.pos, match_obj.start("tel"), match_obj.end("tel"): ', '命名捕獲組tel匹配的內容在字符串中的切片位置: ', m.start('tel'), m.end('tel'))
    print('match_obj.pos, match_obj.span(tel): ', '命名捕獲組tel匹配的內容在字符串中的切片位置: ', m.span('tel'))

輸出結果:

match_obj.group():  匹配到的全部內容:  
    My name is Tom,
    I am 16 years old,
    My tel is 13972773480.
    
match_obj.group(0):  同上:  
    My name is Tom,
    I am 16 years old,
    My tel is 13972773480.
    
match_obj.group(1):  第一個捕獲組匹配到的內容:  Tom
match_obj.group(2):  第二個命名捕獲組匹配到的內容:  16
match_obj.group("tel"):  第三個命名捕獲組匹配到的內容:  13972773480
match_obj.groups():  全部捕獲組匹配到的內容組成的元組對象:  ('Tom', '16', '13972773480')
match_obj.groupdict():  全部命名捕獲組匹配到的內容組成的字典對象:  {'age': '16', 'tel': '13972773480'}
match_obj:  直接打印匹配對象:  <_sre.SRE_Match object; span=(0, 75), match='\n    My name is Tom,\n    I am 16 years old,\n  >
match_obj.string:  傳遞給re.match函數的字符串參數:  
    My name is Tom,
    I am 16 years old,
    My tel is 13972773480.
    
match_obj.re:  傳遞給re.match函數的正則表達式對象:  re.compile('.*name\\s+is\\s+(\\w+).*am\\s+(?P<age>\\d{1,3})\\s+years.*tel\\s+is\\s+(?P<tel>\\d{11}).*', re.DOTALL)
match_obj.pos, match_obj.endpos:  傳遞個match方法的pos和endpos參數:  0 75
match_obj.pos, match_obj.start(1), match_obj.end(1):  第一個捕獲組匹配的內容在字符串中的切片位置:  16 19
match_obj.pos, match_obj.span(1):  第一個捕獲組匹配的內容在字符串中的切片位置:  (16, 19)
match_obj.pos, match_obj.start("tel"), match_obj.end("tel"):  命名捕獲組tel匹配的內容在字符串中的切片位置:  58 69
match_obj.pos, match_obj.span(tel):  命名捕獲組tel匹配的內容在字符串中的切片位置:  (58, 69)

5、說說正則表達式字符串前的r前綴


前面咱們介紹re模塊使用步驟時,第一步就是「編寫一個表示正則表達式規則的Python字符串」,那麼正則表達式與Python字符串之間是什麼關係呢?另外,咱們一般都在一個正則表達式字符串前加上一個前綴r是何用意呢?

由於正則表達式並非Python語言的核心部分(有些應用程序根本就不須要正則表達式,re模塊就像socket或zlib同樣,只是包含在Python中的一個簡單的C語言擴展模塊),也沒有建立專門的語法來表示它們,因此在Python中正則表達式是以Python字符串的形式來編寫並進行處理的。可是,咱們應該知道的是字符串有 字符串的字面值(咱們給一個字符串賦的值)字符串的實際值(這個字符串的打印值),且正則表達式引擎把一個字符串做爲正則表達式處理時,所取的是這個字符串的實際值,而不是它的字面值。

問題1:

字符串的字面值 與 字符串的實際值 不必定是等價的,由於有些字符組合起來表示的是一個特殊的符號,此時會致使正則表達式錯誤而沒法匹配到想要的結果。好比:\1在正則表達式中表示引用第一個捕獲組所匹配到的內容,而在字符串中卻表示一個特殊的字符,所以若是一個表示正則表達式的字符串中包含"\1"且沒有作任何處理的話,它是沒法匹配到咱們想要的結果的。以下圖所示:

若是想要匹配正確結果就須要對該字符串中表示特殊字符的字符組合進行處理,咱們只須要在'\1'前加一個反斜線對'\1'中的反斜線進行轉義就能夠了,即字符串"\1"的字面值纔是\1。以下圖所示:

也就是說,一個正則表達式要想用一個Python字符串來表示時,可能須要對這個字符串的字面值作一些特殊的轉義處理。

問題2:

如今反過來考慮,\s在正則表達式中表示空白字符,若是咱們想匹配一個字符串中'\s'這兩個字符,就須要在正則表達式中\s前也加一個反斜線進行轉義\\s。那麼這時候表示正則表達式的字符串一樣不能直接寫'\\s',由於它是字符串字面值,其實際值是\s,所以此時也是不能匹配到咱們想要的結果的。以下圖所示:

若是想要匹配正確結果就須要對該字符串進行一些處理,咱們須要在'\\s'前加兩個反斜線對'\\s'中的兩個反斜線分別進行轉義,即字符串"\\\\s"的字面值纔是\\s。以下圖所示:

字符串的r前綴

經過上面兩個問題咱們能夠得出如下結論:

  • 1)這些問題都是因爲反斜線引發的,這種現象也被稱爲"反斜線瘟疫";
  • 2)Python字符串與正則表達式都使用反斜線進行字符轉義,正是因爲這種衝突才引發了這些問題;
  • 3)當一個正則表達式中又不少個反斜線要處理時,那無疑將是一種災難。

怎麼辦呢?忘掉上面這全部的問題吧,你只須要在表示正則表達式的字符串前加上一個前綴r就能夠把這個字符串固然正則表達式來寫了。 r是Raw,即「原始」的意思,帶有r前綴的字符串就叫作「Raw String」,即原始字符串的意思。也就是說當一個字符串帶有r前綴時,它的字面值就是其真實值,也就是正則表達式的值。

簡單來講,表示正則表達式匹配規則的字符串前面的r前綴是爲了解決字符串中的反斜線與正則表達式中的反斜線引發的衝突問題。另外,咱們根本無需關心哪些字符會引發這樣的衝突,只須要在每一個表示正則表達式的字符串前加上一個r前綴就能夠了。

6、re模塊綜合應用實例


0. 實例背景與準備工做

假設你正在寫一個撲克遊戲,每張牌分別用一個字符來表示:

  • 'a' 表明老A,也叫「尖兒」
  • 'k' 表明老K
  • 'q' 表明Q,也叫「圈兒」
  • 'j' 表明J, 也叫「鉤兒」
  • 't' 表明10
  • '2'-'9' 分別表明2-9這幾個數字
    另外,遊戲規則要求每一個玩家手中要有5張牌,不能多也不能少。

首先,咱們先來寫一個工具函數來幫助咱們更優雅的展現匹配結果:

def display_match_obj(match_obj):
    if match_obj is None:
        return None
    return '<Match: %r, groups=%r>' % (match_obj.group(), match_obj.groups())

實例1. 首先匹配玩家手中的牌是否符合遊戲規則

匹配規則分析

遊戲規則有兩個:

  • 1)玩家手中的牌只能是'a', 'k', 'q', 'j', 't' 和 '2'-'9'中的字符
  • 2)玩家手中的牌只能有5張
所需知識點
  • 字符羣組元字符:[]
  • 量詞元字符:{n}
  • 邊界匹配元字符:'^'和'$'
正則表達式

正確的正則表達式應該是這樣的:'^[akqjt2-9]{5}$'

匹配測試

下面來看下面幾個匹配測試

import re

p = re.compile(r"^[atjqk2-9]{5}$")

display_match_obj(p.match("akt5q"))
display_match_obj(p.match("akt5e"))
display_match_obj(p.match("akt"))
display_match_obj(p.match("727ak"))
display_match_obj(p.match("aaaak"))

輸出結果:

<Match: 'akt5q', groups=()>
Invalid
Invalid
<Match: '727ak', groups=()>
<Match: 'aaaak', groups=()>

分析:

  • 'akt5e'中的包含非法字符'e',所以匹配不成功
  • 'akt'中字符個數不是5,所以匹配不成功
  • groups爲空元組表示沒有匹配到分組信息

實例2. 匹配玩家手中的牌是否包含對子(兩張同樣的牌)

匹配規則分析

這裏,咱們先不考慮上面的規則,僅僅考慮須要包含兩張同樣的牌。

所需知識點
  • 分組與向後引用:(...)和\n
正則表達式

正確的正則表達式應該是這樣的:'.*(.).*\1.*'

匹配測試

下面來看下面幾個匹配測試

import re

p = re.compile(r".*(.).*\1.*")

display_match_obj(p.match("akt5q"))
display_match_obj(p.match("akt5e"))
display_match_obj(p.match("akt"))
display_match_obj(p.match("727ak"))
display_match_obj(p.match("aaaak"))

輸出結果:

Invalid
Invalid
Invalid
<Match: '727ak', groups=('7',)>
<Match: 'aaaak', groups=('a',)>

分析:

  • 只有'727ak'和'aaaak'中的包含兩個相同的字符,其餘的字符串都不匹配。
  • '727ak'和'aaaak'中被分組匹配到的字符分別是'7'和'a'
思考:若是想匹配包含3張或者4張相同牌,正則表達式該怎樣寫呢?

其實很簡單,只要重複相應次數的 '\1.*' 就能夠了:

  • 匹配包含3張相同牌的正則表達式:'.*(.).*\1.*\1.*'
  • 匹配包含4張相同牌的正則表達式:'.*(.).*\1.*\1.*\1.*'

實例3:把上面兩個例子的匹配需求整合起來

匹配分析

這個要求的難點在於,這兩個匹配需求的匹配規則相對獨立沒法用一個常規的正則表達式匹配模式來表達。要把多個相對獨立的正則表達式整合到一塊兒,咱們有兩種實現方式:

  • 1)匹配兩次:先用第一個例子中的正則表達式進行匹配,匹配經過再進行第二個匹配
  • 2)使用元字符特殊構造:(?=...)
第1種實現方式:匹配兩次
import re

def process_match(string):
    p1 = re.compile(r"^[akqjt2-9]{5}$")
    if p1.match(string):
        p2 = re.compile(r".*(.).*\1.*")
        display_match_obj(p2.match(string))
    else:
        display_match_obj(None)

if __name__ == "__main__":
    process_match("727a")
    process_match("727akk")
    process_match("72tak")
    process_match("727ak")

輸出結果:

Invalid
Invalid
Invalid
<Match: '727ak', groups=('7',)>

分析:

  • '727a'和'727akk'中的字符個數都不知足第一個正則表達式,所以匹配失敗
  • '72tak'雖然知足第一個正則表達式,可是不包含對子,所以也匹配失敗
  • 只有'727ak'既知足第一個正則表達式,又包含對子(一對7),所以匹配成功
  • 可見,兩個正則表達式都生效了
第2種實現方式:使用元字符特殊構造 (?=...)
import re

p = re.compile(r"(?=^[akqjt2-9]{5}$)(?=.*(.).*\1.*)")

display_match_obj(p.match("727a"))
display_match_obj(p.match("727akk"))
display_match_obj(p.match("72tak"))
display_match_obj(p.match("727ak"))

輸出結果:

Invalid
Invalid
Invalid
<Match: '', groups=('7',)>

分析:

  • 由輸出結果可知,一樣也是最後一個匹配成功,所以這個正則表達式'(?=^[akqjt2-9]{5}$)(?=.*(.).*\1.*)'是知足要求的;
  • 另外,發現Match的結果爲空,這是由於特殊構造 (?=...) 在匹配過程當中是不消費字符的,這也就說明這種特殊構造只適合作匹配測試,不能獲取匹配到的內容

7、參考文檔


問題交流羣:666948590

相關文章
相關標籤/搜索