Python中的re模塊--正則表達式
使用match從字符串開頭匹配
以匹配國內手機號為例,通常手機號為11位复唤,以1開頭像樊。大概是這樣13509094747
倡蝙,(這個號碼是我隨便寫的,請不要撥打)踢步,我們通常還能看到其他美觀的顯示形式癣亚。
- 135-0909-4747
- 135 0909 4747
前三位由運營商規(guī)定,這里我們不考慮获印。
如何使用正則表達式匹配類似上面的手機號呢述雾?
import re
result = re.match('\d\d\d-\d\d\d\d-\d\d\d\d', '135-0909-4747')
print(result)
\d
表示匹配一個數(shù)字。于是上面的寫法可以匹配兼丰,但是打印的內容是這樣的
# out
<_sre.SRE_Match object; span=(0, 13), match='135-0909-4747'>
沒有出現(xiàn)None
說明匹配成功了玻孟,字符范圍[0, 13],十一位的手機號加上兩位分隔符-
剛好13位鳍征。match里顯示了匹配成功的字符串黍翎。這樣的結果并不直觀。
使用result.group()
即可提取出match里面的內容蟆技。并且是str類型玩敏,更方便我們處理。
...
print(result.group()) # out: 135-0909-4747
上面的寫法還是太臃腫质礼,result = re.match('\d{3}-\d{4}-\d{4}', '135-0909-4747')
旺聚,這種寫法和上面等價。{}
里面的次數(shù)表示要匹配的次數(shù)眶蕉。當然里面可以填區(qū)間砰粹,區(qū)間是閉區(qū)間,包含左右的數(shù)字。比如
-
\d{3,}
匹配數(shù)字3或者3次以上 -
\d{,9}
匹配數(shù)字0次~9次之間 -
\d{2,4}
匹配數(shù)字2次~4次之間
一定要注意碱璃,填入?yún)^(qū)間的時候弄痹,逗號左右都沒有空格。
如果一個規(guī)則我們經(jīng)常要用到嵌器,可以使用re.compile
編譯成一個pattern object
對象肛真。像這樣
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.match(phone_p, '135-0909-4747')
print(result)
# result = phone_p.match('135-0909-4747')
# print(result)
phone_p
是一個對象,可以用它直接調用match
方法爽航,直接填入要匹配的字符串就好了蚓让。就像上面被注釋掉的地方一樣。也可以使用re.match
讥珍,不同的是历极,第一個參數(shù)需要填上這個模式對象,第二個參數(shù)才是要匹配的字符串衷佃。兩種方法得到的結果一樣趟卸,喜歡哪種用哪種。
使用search搜尋字符串中可能存在的匹配
re還有一個serach
方法氏义,和match
用法極其相似锄列。唯有不同的是,match
要求匹配必須從字符串的開頭開始觅赊,也就是說右蕊,如果第一個字符就不匹配,后面即使有和模式匹配的字符串吮螺,也被認為是匹配失敗饶囚。這么說不好理解。舉個例子鸠补,還是手機號萝风。
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.match(phone_p, 'Bob 135-0909-4747')
print(result)
在手機號前加了機主姓名,我們可以看到紫岩,后面還是以前的手機沒有變规惰,按理說這個模式應該能提取出手機號,但是打印的卻是None
泉蝌,因為使用的是match
匹配歇万,模式中要求是3個數(shù)字打頭,然后給出的字符串以字母開始勋陪。第一個字符就掛掉了贪磺。所以說match是從字符的開頭匹配的。
再看看search
呢诅愚?
只需將match改成search寒锚,輸出<_sre.SRE_Match object; span=(4, 17), match='135-0909-4747'>
表示匹配成功,字符范圍[4:17],不含17刹前∮靖常可以看到search
搜尋字符串里所有可能的情況,一旦發(fā)現(xiàn)有匹配的子字符串就返回喇喉。
為了加深理解祖今,再看這樣的例子
import re
# 注意多了個^
phone_p = re.compile('^\d{3}-\d{4}-\d{4}')
result = re.search(phone_p, 'Bob 135-0909-4747')
print(result) # None
再模式的最前面加上^
表示匹配開始的標志,即必須以^
后的內容開頭拣技,在這句里的意思就是必須以3個數(shù)字開頭(而不是1個衅鹿,\d{3}
是一個整體)」В可以看到,即使是search
方法也不能匹配成功了制妄。因被強制從字符串開頭處開始匹配掸绞,這句的意思不就和和使用match
方法達到同樣的效果了嗎?
說到^
就不得不提$
耕捞,后者是匹配結束的標志衔掸,必須以$
前的字符結尾。
import re
phone_p = re.compile('^\d{3}-\d{4}-\d{4}$')
# 不小心在開頭或者結尾多輸入了一位
result = re.search(phone_p, '135-0909-47475') # or 1135-0909-4747
print(result) # None
顯然結是4個數(shù)字結尾(或不是3個數(shù)字開頭)俺抽,返回None敞映。
這句模式限制了必須是11位的數(shù)字加分隔符組成。多一位少一位都不行磷斧。
還有一個地方要注意振愿,不管是match
還是search
,即使可能存在多個正確的匹配弛饭,它們找到第一個后就立即停止冕末,所有我們得到的永遠是第一個成功匹配的字符串。
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.search(phone_p, 'My phone number is 135-0909-4747 and another is 123-4567-8901')
print(result) # 135-0909-4747
找到第一個手機號就不在匹配了侣颂,第二個手機號被忽略了档桃。
使用findall找到所有成功的匹配
上面的例子,如何找到所有的手機號呢憔晒?用re.findall
藻肄,它返回所有成功匹配字符串的列表.
import re
phone_p = re.compile('\d{3}-\d{4}-\d{4}')
result = re.findall(phone_p, 'My phone number is 135-0909-4747 and another is 123-4567-8901')
print(result)
僅是將search換成findall,會打印['135-0909-4747', '123-4567-8901']
可以看到拒担,所有的手機號都被找到了嘹屯!
在正則表達式中盡量使用原始字符串
由于正則表達式中經(jīng)常要用到\
,而轉義字符可能影響到我們的模式表達澎蛛。
p = re.compile('gg\\d')
p_1 = re.compile('gg\d')
print('\d') # \d
print('\\d') # \d
上面的例子抚垄,打印結果都一樣\d
,因為\d
沒有對應的轉義。兩種模式的寫法也沒有區(qū)別呆馁。
但是有些字符是可以轉義的桐经,比如n。
print('\n') # 換行
print('\\n') # \n
上面例子浙滤,結果就不一樣了阴挣。又回到正則表達式中來
p_0 = re.compile('gg\n') # 匹配'gg\n', \n換行
p_1= re.compile('gg\\n') # 匹配'gg\n', \n換行
# 使用了原始字符串
p_2 = re.compile(r'gg\n') # 匹配'gg\n'纺腊,\n換行
p_3 = re.compile(r'gg\\n') # 匹配'gg\\n', \n字符串
可以看到?jīng)]有使用原始字符串時候畔咧,會讓人迷惑,上述前兩行揖膜,兩種匹配模式匹配的都是gg和一個換行符誓沸。使用了原始字符串就比較清楚了,待匹配的字符串(就不要再使用原始字符串了)壹粟,和模式對應起來了拜隧,不會混淆,如上述的最后兩行代碼趁仙。
當然打印的時候又會有些不一樣
print('gg\\n') # gg\n
print('gg\n') # gg換行
print(r'gg\n') # gg\n
print(r'gg\\n') # gg\\n
打印時洪添,原始字符串完全忽略了\
對字符的轉義,字符串里是啥樣雀费,打印出來就是啥樣干奢。
在正則表達式里面的原始字符串(對\
還是有一定程度的影響)和打印時候的原始字符串還時有點差別的。
原始字符串在處理文件路徑時相當有用盏袄。
# 這么寫不對忿峻,會被轉義,結果就是路徑錯了
filepath = 'F:\nb\person\a.txt'
# 保險一點的做法,用\\將自身轉義辕羽,表示真正意義上的'\'炭菌,
filepath = 'F:\\nb\person\\a.txt'
# 使用原始字符串
filepath = r'F:\nb\person\a.txt'
當然了,直接用Linux/OS X的路徑方式在Windows上貌似也是可以的逛漫。直接遠離了轉義字符的困擾黑低。
filepath = 'F:/nb/person/a.txt'
也可以運行成功,沒問題酌毡。
討論了這么多其實就想說克握,正則表達式編譯模式時,盡可能地使用原始字符串枷踏。
高級匹配模式
"[]"匹配集合里面的任意一個字符
import re
p = re.compile(r'[朱劉馬]帥吃飯了嗎')
result = re.match(p, '馬帥吃飯了嗎') # or 朱帥吃飯了嗎 or 劉帥吃飯了嗎
print(result)
[]
里面的內容表示任意一個字符菩暗,只要在這個集合里面的就能匹配成功。所以上面的模式可以匹配
馬帥吃飯了嗎
朱帥吃飯了嗎
劉帥吃飯了嗎
這是針對單個字符的旭蠕,還可以這樣寫[a-z0-9]
代表一個范圍停团。這表示一個字符只要是字母或者數(shù)字就能匹配成功旷坦,當然后面可以加上{}
。p = re.compile(r'[0-9]{3}')
可以匹配3位數(shù)字佑稠,其實和\d+{3}
異曲同工秒梅。
"|"匹配這個或那個字符串
上面的例子還可以這樣寫。
import re
p = re.compile(r'朱|劉|馬帥吃飯了嗎')
result = re.match(p, '馬帥吃飯了嗎')
print(result)
效果和上面一樣舌胶。這是單個字符的時候捆蜀,來看看涉及到特定的多個字符時候。
import re
p = re.compile(r'Bob|Jerry|Tom Lee')
result = re.match(p, 'Jerry Lee')
print(result)
這能匹配三個人名
Bob Lee
Jerry Lee
Tom Lee
如果使用[]
就不好操作了幔嫂。下面也能匹配上面的三個名字辆它,不過哪個更易懂不言而喻。所以要分場合用最合適的履恩。
p = re.compile(r'[BJT][oe][brm][\sry]{,2} Lee')
還有一點锰茉,[]
里可以使用^
表示“非”的意思。
p = re.compile(r'[^0-9]')
這就表示切心,除開數(shù)字的其他任意一個字符洞辣。
“?”匹配0次或者1次
import re
p = re.compile(r'我有一萬?元')
result = re.match(p, '我有一元') # or我有一萬元
print(result)
“萬”字匹配0次(沒有)或者1次都是成功的。通俗點講昙衅,這個字符時可選的。其實用?
可以看成是p = re.compile(r'我有一萬{,1}元')
的簡寫定鸟。
“*”匹配任意次, "+"匹配至少1次
*
可以匹配0次而涉,也可以匹配多次。實際上可看作p = re.compile(r'我有一萬{0,}元')
+
匹配至少一次联予,可以看作p = re.compile(r'我有一萬{1,}元')
.這意味著它不能匹配我有一元
啼县,必須含有一個或者多個“萬”字。
貪婪匹配和非貪婪
Python的正則表達式默認是貪婪匹配沸久。這意味著它將盡可能多的季眷,盡可能往后匹配。只要后面還有能成功匹配的字符串卷胯,就不會停下來子刮。
比如
import re
p = re.compile(r'我有一萬*')
result = re.match(p, '我有一萬萬萬萬萬')
print(result)
雖然*
可匹配0次,1次...多次窑睁。但是不是返回我有一
或者我有一萬
挺峡,而是后面有多少就匹配到多少。
如果要變成非貪婪匹配呢担钮?后加?
p = re.compile(r'我有一萬*?')
result = re.match(p, '我有一萬萬萬萬萬')
這樣就會盡可能少的匹配橱赠,因為*
最少能匹配0次,所以這里返回我有一
箫津。
注意狭姨,這里的?不要解釋成0次或者1次宰啦,在非貪婪里面的?
和上面介紹的?
是有差別的。
通配字符"."
.
可以匹配除了換行符之外的所有字符饼拍,如果加入標志位flags=re.DOTALL
赡模,使得.
什么都可以匹配(包括換行符),還有re.IGNORECASE
和re.VERBOSE
# re.DOTALL
p = re.compile(r'good.haha', re.DOTALL)
result = re.findall(p, 'good\nhaha')
# 按位或可以同時使用兩種模式
p = re.compile(r'good.haha', re.IGNORECASE | re.DOTALL)
result = re.findall(p, 'GOOD\nHahA')
# re.VERBOSE可以忽略空白字符和注釋惕耕,當模式比較復雜時這樣可能會直觀點
p = re.compile(r'''
\w+. # asdf
\w+''' # some..
, re.IGNORECASE | re.DOTALL | re.VERBOSE)
result = re.findall(p, 'GOOD\nHahA')
順便一提纺裁,\w
匹配單詞字符,它包括了數(shù)字
搭配*
和?
更好用
.* 貪婪匹配所有字符
.*? 非貪婪匹配所有字符
舉個例子
import re
#貪婪
p = re.compile(r'abcd.*1234', re.DOTALL)
result = re.findall(p, 'abcdDAMN1234IT1234')
print(result) # ['abcdDAMN1234IT1234']全部匹配
# 非貪婪
p = re.compile(r'abcd.*?1234', re.DOTALL)
result = re.findall(p, 'abcdDAMN1234IT1234')
print(result) # ['abcdDAMN1234']遇到第一個1234就停止
使用捕獲組
上面的例子如果使用()
將.*?
包含起來司澎,在findall
下將只返回括號里的內容欺缘,這很有用,往往我們需要的只是那里面的內容挤安。
import re
p = re.compile(r'abcd(.*?)1234', re.DOTALL)
result = re.findall(p, 'abcdFUCK1234')
# out: ['FUCK']
print(result)
如果有多個括號呢谚殊?
import re
p = re.compile(r'[a-z]+((\d+)-(\d+))[a-z]+')
print(result.group(1))
print(result.group(2))
print(result.group(3)))
result = re.findall(p, 'afs123-456gds')
print(result)
可以看到,我們把數(shù)字用括號包起來了蛤铜,這里有3個括號嫩絮。輸出是這樣的
[('123-456', '123', '456')]
列表里面實際上是一個元組,分別對應了三個括號里面的值围肥。如果覺得findall
返回的形式不夠清楚剿干,可以用group
p = re.compile(r'[a-z]+((\d+)-(\d+))[a-z]+')
result = re.match(p, 'afs123-456gds')
print(result.group()) # afs123-456gds
print(result.group(1)) # 123-456
print(result.group(2)) # 123
print(result.group(3)) # 456
group()
或者group(0)
意思一樣,永遠放回匹配成功的整個字符串穆刻。貌似和括號沒有什么關系置尔。不過要是使用group(1)
查看下就會發(fā)現(xiàn),它返回了第一個分組里的內容氢伟。上面共有3個分組榜轿,所以最多group(3)
,group(4)
就要報錯了朵锣。發(fā)現(xiàn)Python將最外層的括號視為第一組谬盐,里面的分組按照從左到右的順序依次為第二組、第三組诚些。
還能使用groups()
方法飞傀,返回所有分組(注意和group()區(qū)分)
('123-456', '123', '456')
按照順序依次是第一第二第三組,這和用findall
返回的數(shù)據(jù)一樣(只是少了列表包圍)
分割字符串
使用re.split()
import re
# 以這個模式為分隔符
p = re.compile(r'\d+')
result = re.split(p, 'tom32jerry456haha')
print(result) # ['tom', 'jerry', 'haha']
可以看到诬烹,以數(shù)字為分隔符助析,將單詞提取出來了。
字符串的替換
還是上面的例子椅您,上面以數(shù)字分割外冀,這次讓漢字替換掉數(shù)字。
import re
p = re.compile(r'\d+')
result = re.sub(p, '中文', 'tom32jerry456haha')
print(result) # tom中文jerry中文haha
如果要用到匹配得文本本身掀泳,可以使用\1
和\2
這樣的形式雪隧,表示使用分組得第一組和第二組西轩,\0
沒有這樣的寫法,這會被當成空字符串
import re
p = re.compile(r'(\d+)abcd(\d+)')
result = re.sub(p, r'\2invert\1', '12345abcd67890')
print(result) # 67890invert12345
有兩個分組脑沿,r'\2\1'
這里要使用原始字符串藕畔,不用的話自己試試看輸出啥東西。
表示用分組2invert分組1
得內容替代原字符串庄拇。由于分組1為12345注服,分組2為67890,所以是使用了67890invert12345
代替了原字符串.
哦對了措近,平常還有一個用得比較多溶弟。\s
可以匹配空格/換行符/制表符等等空白字符。其他的瞭郑,用到的時候再查表吧辜御!
針對我個人日常得使用,掌握這么多應該差不多了屈张。不過有個博客總結得更詳細擒权,推薦Python正則表達式指南
by @sunhaiyu
2017.6.24