提示: 因爲該站對MARKDOWN的表格支持的不是很好,因此本文中的表格均以圖片的形式提供,你們若是看着比較模糊,能夠放大來看或下載圖片在本地查看。html
正則表達式(Regluar Expressions)又稱規則表達式,在代碼中常簡寫爲REs,regexes或regexp(regex patterns)。它本質上是一個小巧的、高度專用的編程語言。 經過正則表達式能夠對指定的文本實現
匹配測試、內容查找、內容替換、字符串分割 等功能。正則表達式的語法不是本節要講的內容(關於正則表達式的詳細介紹請參考另外一篇博文《正則表達式總結》),本節主要介紹的是Python中是如何使用re模塊來完成正則表達式的相關操做的。python
Python中的re模塊提供了一個正則表達式引擎接口,它容許咱們將正則表達式編譯成模式對象,而後經過這些模式對象執行模式匹配搜索和字符串分割、子串替換等操做。re模塊爲這些操做分別提供了模塊級別的函數以及相關類的封裝。正則表達式
Python中的re模塊中最重要的兩個類:編程
類 | 描述 |
---|---|
Regular Expression Objects | 正則表達式對象,用於執行正則表達式相關操做的實體 |
Match Objects | 正則表達式匹配對象,用於存放正則表達式匹配的結果並提供用於獲取相關匹配結果的方法 |
經過re模塊的compile()函數編譯獲得的正則表達式對象(下面用regex表示)支持以下方法:socket
說明: 若是指定了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()獲得的結果就是一個匹配對象,匹配對象支持如下方法和屬性:函數
re模塊提供瞭如下幾個模塊級別的函數工具
將pattern參數只能是字符串;測試
說明: 經過對比會發現,上面這些re模塊級別的函數除了re.compile、re.purge和re.escape這幾個函數外,其它函數名都與正則表達式對象支持的方法同名。實際上re模塊的這些函數都是對正則表達式對象相應方法的封裝而已,功能是相同的。只是少了對pos和endpos參數的支持,可是咱們能夠手動經過字符串切片的方式來達到相應的需求。我的認爲,咱們應該儘量的使用模塊級別的函數,這樣能夠加強代碼的兼容性。ui
由上面的描述可知,flags參數在上面這些模塊函數中是要個可選參數,re模塊中預約了該參數可取的值:
說明: 這些flag能夠單獨使用,也能夠經過邏輯或操做符'|'進行拼接來聯合使用。
咱們有必要對re模塊中所包含的類及其工做流程進行一下簡單的、總體性的說明,這講有利於咱們對下面內容的理解。
總結: 關於正則表達式的語法和編寫示例請參考《正則表達式總結》)。根據上面的描述可知,將一個表示正則表達式的Python字符串編譯成一個正則表達式對象是使用正則表達式完成相應功能的首要步驟,re模塊中用於完成正則表達式編譯功能的函數爲re.compile()。
在上面的內容中,咱們已經列出了re模塊所提供的類,以及這些類的對象所支持的函數和屬性,還有re模塊所提供的模塊級別的函數。這裏咱們要討論的是怎樣合理的使用這些方法和函數,換句話說就是在什麼狀況下使用這些方法和函數,以及如何使用的問題。咱們以前說過,正則表達式主要能夠用來提供如下幾個功能:
下面咱們就分別經過對這幾個功能的實例實現對上面這些函數和方法以及參數和屬性的使用作下說明和具體的解釋。
匹配測試,意思是經過特定的正則表達式對一個指定的字符串進行匹配來判斷該字符串是否符合這個正則表達式所要求的格式。常見的使用場景舉例:
經過re模塊執行匹配測試時可使用的函數或方法是:
前面提到過,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:]的開頭部分匹配
上面使用的是正則表達式對象的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參數,因此只能經過字符串切片先對字符串進行切割。
再來看個對比:
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()原本就是從字符串開頭開始匹配的;可是,若是匹配過程不是從字符串的第一個字符開始匹配時,它們是有區別的,具體請看下面這個例子。
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()也將只匹配字符串的開始位置,而不是該字符串每一行的行首。
內容查找,意思是經過特定的正則表達式對一個指定的字符串的內容進行掃描來判斷該字符串中是否包含與這個正則表達式相匹配的內容。
經過re模塊執行匹配測試時可使用的函數或方法是:
這個例子中,咱們來看下search()函數的使用,以及它與match()函數的對比。
Python提供了兩個不一樣的基於正則表達式的簡單操做:
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'>
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
傳統的字符串操做只能替換明確指定的子串,而使用正則表達式默認是對一個字符串中全部與正則表達式相匹配的內容進行替換,也能夠指定替換次數。
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):
經過正則表達式對字符串進行分割的過程是:掃描整個字符串,查找與正則表達式匹配的內容,而後以該內容做爲分割符對字符串進行分割,最終返回被分割後的子串列表。這對於將文本數據轉換爲Python易於讀取和修改的結構化數據很是有用。
咱們能夠經過maxsplit參數來限制最大切割次數。
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個元素。
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.
咱們如今要使用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總體返回。
當咱們經過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')。
咱們在上面介紹匹配對象所提供的方法時對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)
前面咱們介紹re模塊使用步驟時,第一步就是「編寫一個表示正則表達式規則的Python字符串」,那麼正則表達式與Python字符串之間是什麼關係呢?另外,咱們一般都在一個正則表達式字符串前加上一個前綴r是何用意呢?
由於正則表達式並非Python語言的核心部分(有些應用程序根本就不須要正則表達式,re模塊就像socket或zlib同樣,只是包含在Python中的一個簡單的C語言擴展模塊),也沒有建立專門的語法來表示它們,因此在Python中正則表達式是以Python字符串的形式來編寫並進行處理的。可是,咱們應該知道的是字符串有 字符串的字面值(咱們給一個字符串賦的值) 和 字符串的實際值(這個字符串的打印值),且正則表達式引擎把一個字符串做爲正則表達式處理時,所取的是這個字符串的實際值,而不是它的字面值。
字符串的字面值 與 字符串的實際值 不必定是等價的,由於有些字符組合起來表示的是一個特殊的符號,此時會致使正則表達式錯誤而沒法匹配到想要的結果。好比:\1在正則表達式中表示引用第一個捕獲組所匹配到的內容,而在字符串中卻表示一個特殊的字符,所以若是一個表示正則表達式的字符串中包含"\1"且沒有作任何處理的話,它是沒法匹配到咱們想要的結果的。以下圖所示:
若是想要匹配正確結果就須要對該字符串中表示特殊字符的字符組合進行處理,咱們只須要在'\1'前加一個反斜線對'\1'中的反斜線進行轉義就能夠了,即字符串"\1"的字面值纔是\1。以下圖所示:
也就是說,一個正則表達式要想用一個Python字符串來表示時,可能須要對這個字符串的字面值作一些特殊的轉義處理。
如今反過來考慮,\s在正則表達式中表示空白字符,若是咱們想匹配一個字符串中'\s'這兩個字符,就須要在正則表達式中\s前也加一個反斜線進行轉義\\s
。那麼這時候表示正則表達式的字符串一樣不能直接寫'\\s'
,由於它是字符串字面值,其實際值是\s,所以此時也是不能匹配到咱們想要的結果的。以下圖所示:
若是想要匹配正確結果就須要對該字符串進行一些處理,咱們須要在'\\s'
前加兩個反斜線對'\\s'
中的兩個反斜線分別進行轉義,即字符串"\\\\s"
的字面值纔是\\s
。以下圖所示:
經過上面兩個問題咱們能夠得出如下結論:
怎麼辦呢?忘掉上面這全部的問題吧,你只須要在表示正則表達式的字符串前加上一個前綴r就能夠把這個字符串固然正則表達式來寫了。 r是Raw,即「原始」的意思,帶有r前綴的字符串就叫作「Raw String」,即原始字符串的意思。也就是說當一個字符串帶有r前綴時,它的字面值就是其真實值,也就是正則表達式的值。
簡單來講,表示正則表達式匹配規則的字符串前面的r前綴是爲了解決字符串中的反斜線與正則表達式中的反斜線引發的衝突問題。另外,咱們根本無需關心哪些字符會引發這樣的衝突,只須要在每一個表示正則表達式的字符串前加上一個r前綴就能夠了。
假設你正在寫一個撲克遊戲,每張牌分別用一個字符來表示:
首先,咱們先來寫一個工具函數來幫助咱們更優雅的展現匹配結果:
def display_match_obj(match_obj): if match_obj is None: return None return '<Match: %r, groups=%r>' % (match_obj.group(), match_obj.groups())
遊戲規則有兩個:
正確的正則表達式應該是這樣的:'^[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=()>
分析:
這裏,咱們先不考慮上面的規則,僅僅考慮須要包含兩張同樣的牌。
正確的正則表達式應該是這樣的:'.*(.).*\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',)>
分析:
其實很簡單,只要重複相應次數的 '\1.*'
就能夠了:
'.*(.).*\1.*\1.*'
'.*(.).*\1.*\1.*\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',)>
分析:
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的結果爲空,這是由於特殊構造
(?=...)
在匹配過程當中是不消費字符的,這也就說明這種特殊構造只適合作匹配測試,不能獲取匹配到的內容。
問題交流羣:666948590