Python中的re模塊--正則表達式

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.IGNORECASEre.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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市阁谆,隨后出現(xiàn)的幾起案子碳抄,更是在濱河造成了極大的恐慌,老刑警劉巖场绿,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剖效,死亡現(xiàn)場離奇詭異,居然都是意外死亡裳凸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門劝贸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姨谷,“玉大人,你說我怎么就攤上這事映九∶蜗妫” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵件甥,是天一觀的道長捌议。 經(jīng)常有香客問我,道長引有,這世上最難降的妖魔是什么瓣颅? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮譬正,結果婚禮上宫补,老公的妹妹穿的比我還像新娘檬姥。我一直安慰自己,他們只是感情好粉怕,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布健民。 她就那樣靜靜地躺著,像睡著了一般贫贝。 火紅的嫁衣襯著肌膚如雪秉犹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天稚晚,我揣著相機與錄音崇堵,去河邊找鬼。 笑死蜈彼,一個胖子當著我的面吹牛筑辨,可吹牛的內容都是我干的。 我是一名探鬼主播幸逆,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼棍辕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了还绘?” 一聲冷哼從身側響起楚昭,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拍顷,沒想到半個月后抚太,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡昔案,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年尿贫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踏揣。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡庆亡,死狀恐怖,靈堂內的尸體忽然破棺而出捞稿,到底是詐尸還是另有隱情又谋,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布娱局,位于F島的核電站彰亥,受9級特大地震影響婶恼,放射性物質發(fā)生泄漏速缨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一露筒、第九天 我趴在偏房一處隱蔽的房頂上張望耻涛。 院中可真熱鬧仁卷,春花似錦穴翩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丰介,卻和暖如春背蟆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哮幢。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工带膀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人橙垢。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓垛叨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親柜某。 傳聞我的和親對象是個殘疾皇子嗽元,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • re模塊手冊 本模塊提供了和Perl里的正則表達式類似的功能,不關是正則表達式本身還是被搜索的字符串喂击,都可以...
    喜歡吃栗子閱讀 3,981評論 0 13
  • 初衷:看了很多視頻剂癌、文章,最后卻通通忘記了翰绊,別人的知識依舊是別人的佩谷,自己卻什么都沒獲得。此系列文章旨在加深自己的印...
    DCbryant閱讀 3,982評論 0 20
  • 老張我 沒上過大學 教你們 還是綽綽有余的 說完 就用方言 把《陋室銘》 背一遍 坐在最后一排 的二牛站起來 老師...
    關中陳鏡閱讀 129評論 0 0
  • 作者:blue(又名一書and一世界)我的github 適用對象:ubuntu使用者 問題:terminator+...
    一書and一世界閱讀 976評論 0 0
  • 今天母親節(jié)监嗜,所以給媽媽畫了一張谐檀,嘿嘿。明天醒了裁奇,就發(fā)給媽媽桐猬。。 這張是原圖框喳! 明天课幕,祝全天下最偉大的母親厦坛,母親節(jié)快...
    萌283閱讀 263評論 0 0