【python語法】正則表達式

為什么要用正則表達式

對字符串進行操作幾乎是每種編程語言中最重要的功能之一。很簡單就可以理解,因為人類進行信息傳播主要靠的是文字,也就是字符串,但是這么多信息并不完全是我們所要的涎嚼,所以我們會通過編程來提取或者驗證字符串的部分。

正則表達式就是用來匹配字符串的工具挑秉,其實它定義了一套語法法梯,用若干描述字符就可以匹配出某段字符串的特征來。凡是符合種描述規(guī)則的犀概,我們就認為它匹配立哑。

所以比如我們要判斷一串字符是否為合法的Email地址的方法就是:

  • 創(chuàng)建一個符合Email特征的正則表達式
  • 然后使用該正則表達式去匹配輸入的字符串,以判斷是否合法姻灶。

正則表達式

元字符

用\d可以匹配一個數(shù)字铛绰,\w可以匹配一個字母或數(shù)字

元字符 匹配
. 任意字符(但是不包括換行符\n\r等
\w 字母 or 數(shù)字 or 下劃線
\s 空白符(包括Tab等)
\d 數(shù)字

舉個例子 'py.'可以匹配'pyc''pyo'产喉、'py!'等等捂掰。因為.表示的是任意字符,所以可以匹配正常的字母曾沈,也可以匹配!

注意一個元字符只代表一個字符这嚣,比如\w只代表一個字母或者數(shù)字。

可以用[]表示范圍塞俱,比如[0-9]表示匹配0~9之間的任意一個數(shù)字

  • [0-9a-zA-Z\_]可以匹配一個數(shù)字姐帚、字母或者下劃線,可以等價于\w

有時需要查找不屬于某個能簡單定義的字符類的字符障涯,這就是反義

代碼/語法 匹配
[^x] 除了x以外的任意字符
[^aeiou] 除了aeiou這幾個字母以外的任意字符

匹配變長的

如果好匹配變長的字符罐旗,用*表示0個或者以上的字符膳汪,用+表示1個或者以上的字符,用?表示0個或者1個字符九秀。

還可以用大括號來表示旅敷,用{n}表示n個字符,用{n,m}表示n-m個字符颤霎。

代碼/語法 說明
* 重復0次以上,等價于{0,}
+ 重復1次以上涂滴,等價于{1,}
? 重復0次或者1次,等價于{0,1}
{n} 重復n次
{n,} 重復n次以上
{n,m} 重復n到m次

所以比如\d{3}\s+\d{3,8}可以匹配哪些類型的字符串呢友酱?
從左到右讀一下:

  • \d{3}表示匹配3個數(shù)字,例如'010'柔纵;
  • \s可以匹配一個空格(也包括Tab等空白符)缔杉,所以\s+表示至少有一個空格,例如匹配' '搁料,' '等或详;
  • \d{3,8}表示3-8個數(shù)字,例如'1234567'郭计。

如果要匹配'010-12345'這樣的號碼呢霸琴?由于'-'是特殊字符,在正則表達式中昭伸,要用''轉義梧乘,所以,上面的正則是\d{3}-\d{3,8}庐杨。

  • [0-9a-zA-Z\_]+可以匹配至少由一個數(shù)字选调、字母或者下劃線組成的字符串,比如'a100'灵份,'0_Z'仁堪,'Py3000'等等;

  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下劃線開頭填渠,后接任意個由一個數(shù)字弦聂、字母或者下劃線組成的字符串,也就是Python合法的變量氛什;

  • [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精確地限制了變量的長度是1-20個字符(前面1個字符+后面最多19個字符)横浑。

注意與通配符區(qū)分,linux的bash命令行中可以使用通配符屉更,用*來代理任意個的字符徙融。對于正則表達式而言,必須使用.*來表示任意個字符

那么對之前電話號碼的那個例子瑰谜,我們可以用更復雜的表達式來匹配\(?0\d{2}[) -]?\d{8}欺冀。\(?0\d{2}[) -]?\d{8}树绩。,可以匹配(010)88886666隐轩,或022-22334455饺饭,或02912345678等。

  • 首先是一個轉義字符(,它能出現(xiàn)0次或1次(?),
  • 然后是一個0职车,后面跟著2個數(shù)字(\d{2})瘫俊,
  • 然后是)或-或空格中的一個,它出現(xiàn)1次或不出現(xiàn)(?)悴灵,
  • 最后是8個數(shù)字(\d{8}

但是這個表達式也能匹配010)12345678或(022-87654321這樣的“不正確”的格式扛芽。后面會說怎么樣修改就可以解決這個問題。

邊界限定符

邊界限定 匹配
^ 字符串的開始
$ 字符串的結束

比如^\d{5,12}$表示以數(shù)字開頭积瞒,以數(shù)字結尾川尖,整行匹配,同時長度在5~12位一串數(shù)字茫孔。

分支條件

所謂分支條件就類似邏輯中的“或”叮喳,滿足任意一個條件即匹配。具體方法是用|把不同的規(guī)則分隔開

比如之前講過的匹配電話號碼的例子缰贝。

  • 0\d{2}-\d{8}|0\d{3}-\d{7}這個表達式能匹配
    • 三位區(qū)號馍悟,8位本地號(如010-12345678),
    • 4位區(qū)號剩晴,7位本地號(0376-2233445)赋朦。
  • \(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}:這個表達式被|分為兩個條件
    • 左邊的表達式:\(0\d{2}\)可以匹配(010),[- ]?表示之間的連接符可以為-,也可以用空格間隔李破,也可以沒有宠哄。
    • 右邊的表達式0\d{2}[- ]?\d{8}:表示區(qū)號不用小括號括起來。

注意:匹配分枝條件時嗤攻,將會從左到右地測試每個條件毛嫉,如果滿足了某個分枝的話,就不會去再管其它的條件了妇菱。

分組

之前提到的是怎么重復單個字符(直接在字符后面加上限定符就行了)承粤;
但如果想要重復多個字符又該怎么辦?可以用小括號來指定子表達式(也叫做分組)闯团,然后你就可以指定這個子表達式的重復次數(shù)了

比如(\d{1,3}\.){3}\d{1,3}可以按順序進行分析辛臊,

  • \d{1,3}匹配1到3位的數(shù)字,
  • (\d{1,3}.){3}匹配三位數(shù)字加上一個英文句號(這個整體也就是這個分組)重復3次房交,
  • 最后再加上一個一到三位的數(shù)字(\d{1,3})彻舰。

總結

相信突然一下出現(xiàn)這么的符號大家一定是懵逼的。下面我們來總結一下{}[]刃唤, ()這幾種符號的用途隔心。

  • {2,3}:需要與它前面的字符結合,比如a{2,3}表示a出現(xiàn)2~3次
  • []:有3層含義
    • [a-z]:表示一個范圍尚胞,也就是a~z之間的一個字符
    • [.*]:只要放入了[]里面的.*都不表示之前的含義硬霍,只是單純作為一個普通的符號而已。比如這里面就表示要么為點號要么為星號的符號笼裳。
    • [^a]:表示非a的所有字符唯卖。主要不要和^a混淆,^a表示以a開頭的一行躬柬。

貪婪匹配與懶惰匹配

a.*b來說 拜轨,它將匹配最長的以a開始,以b結束的字符串楔脯,比如用它來搜索aabab的時候,會匹配整個字符串aabab胯甩,這就是貪婪匹配昧廷,也就是盡可能多的匹配

那么懶惰匹配指的就是盡可能少的匹配字符。在.*后面加上一個?以后偎箫,可以轉換為懶惰匹配模式木柬,那么.*?意味著使匹配成功的前提下使用最少的重復。比如把它應用于aabab淹办,會匹配aab和ab

為什么第一個匹配是aab而不是ab眉枕?因為正則表達式有一條規(guī)則:最先開始的匹配擁有最高的優(yōu)先權

| 代碼/語法 | 說明 |
|-|
| *? | 重復任意次,但盡可能少重復 |
| +? | 重復1次或更多次怜森,但盡可能少重復 |
| ?? | 重復0次或1次速挑,但盡可能少重復 |
| {n,m}? | 重復n到m次,但盡可能少重復 |
| {n,}? | 重復n次以上副硅,但盡可能少重復 |

匹配漢字

匹配漢字的表達式為[\u4E00-\u9FA5]姥宝,這是漢字的UTF-8編碼的范圍。

python調用正則表達式

Python提供re模塊恐疲,包含所有正則表達式的功能腊满。由于Python的字符串本身也用\轉義,所以要特別注意:
比如python字符串s = 'ABC\\-001'對應的正則表達式變成'ABC\-001'
所以最好把python字符串上加上r前綴培己,就不用考慮轉義的問題碳蛋,比如s = r'ABC\-001' # Python的字符串

如何判斷正則表達式是否匹配:

  • 引入re模塊:import re
  • 使用match方法,如果匹配成功省咨,返回一個Match對象肃弟,否則返回None
    test = '用戶輸入的字符串'
if re.match(r'正則表達式', test):
    print('ok')
else:
    print('failed')

切分字符串

使用正則表達式后,切分字符變得更靈活零蓉。

如下使用split 的正常切分代碼愕乎,可以看出無法識別連續(xù)的空格

>>> 'a b   c'.split(' ')
['a', 'b', '', '', 'c']

使用正則表達式可以實現(xiàn)更復雜的切分:

>>> re.split(r'[\s\,\;]+', 'a,b;; c  d')
['a', 'b', 'c', 'd']

分組

除了判斷是否匹配之外阵苇,正則表達式可以提取子串的強大功能。用()表示的就是要提取的分組(Group)感论。
比如

m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')

這個正則表達式定義了兩個分組绅项,可以匹配-前后的兩個表達式。

  • m.group(0):獲得的是'010-12345'
  • m.group(1):獲得是“010”
  • m.group(2):獲得是'12345'

group(0)永遠是原始字符串比肄,group(1)快耿、group(2)……表示第1、2芳绩、……個子串掀亥。

貪婪匹配

正則表達式默認就是貪婪匹配的。比如

>>> re.match(r'^(\d+)(0*)$', '102300').groups()
#結果是('102300', '')妥色,\d+采用貪婪匹配搪花,直接把后面的0全部匹配了,結果0*只能匹配空字符串了

必須讓\d+采用非貪婪匹配(也就是盡可能少匹配)嘹害,才能把后面的0匹配出來撮竿,加個?就可以讓\d+采用非貪婪匹配:

>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')

再比如

import re 
line = "boooooobby123";
reg_str = ".*(b.*b).*";
match_obj = re.match (reg_str , line);
if match_obj:
    print (match_obj.group(1));

因為.*是貪婪匹配的,所以它會一直匹配到booooooboooooo笔呀,那么小括號里面實際只匹配了bb

正則

如果使用非貪婪模式幢踏,也就是在.*后面加一個?

import re 
line = "boooooobby123";
reg_str = ".*?(b.*?b).*";
match_obj = re.match (reg_str , line);
if match_obj:
    print (match_obj.group(1)); 
image.png

例子:提取日期

下面我們希望能自動化的把一段文字中的生日給提取出來,但是如果之前沒有規(guī)定格式的話许师,大家會隨心所欲的寫日期房蝉,比如

  • 出生于2018年1月23日
  • 出生于2018/1/23
  • 出生于2018-1-23
  • 出生于2018-01-23
  • 出生于2018-01
  • 出生于2018年01月

下面我們需要給一個正則表達式,要求他能匹配上面所有的日期格式微渠。

  • 首先匹配日期中的年的部分搭幻,從上面的文本可以看出,只有2018年逞盆、2018-粗卜,
    2018/這幾種形式。也就是可以先用\d{4}表示數(shù)字纳击,再用[年-\]來表示符號续扔。湊起來就是
regex = r"出生于(\d{4}[年/-])"
  • 再來看月份的數(shù)字部分只可能有011兩種形式:\d{1,2}
  • 月份后面的部分就相對比較復雜了。同樣的焕数,我們可以進行分類列舉纱昧,然后使用分支條件即可統(tǒng)一表達。
    • 匹配2018年1月23日2018-01-23以及2018/1/23后面的部分:[月/-]\d{1,2}日?
    • 匹配2018年01月這種的后面的部分:[月/-]$
    • 匹配2018-01后面的部分堡赔,當然是直接用結尾符:$
    • 最后用()括起來识脆,使用|進行分類討論。
([月/-]\d{1,2}日?|[月/-]$|$)

最后把所有的部分合并起來。

import re 
lines = [
 "出生于2018年1月23日",
 "出生于2018/1/23",
 "出生于2018-1-23",
 "出生于2018-01-23",
 "出生于2018-01",
 "出生于2018年01月"]
regex = r"出生于(\d{4}[年/-]\d{1,2}([月/-]\d{1,2}日?|[月/-]$|$))"
for line in lines :
    m = re.match(regex  , line )
    if m :
        print(m.group(1));
image.png

編譯

使用正則表達式時灼捂,re模塊內部會干兩件事情:

  • 編譯正則表達式离例,此時會進行語法分析,如果表達式本身不合法悉稠,會報錯宫蛆;
  • 用編譯后的正則表達式去匹配字符串。
    那么如果一個正則表達式要使用非常多次的猛,可以預編譯該正則表達式
# 編譯:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')

參考

廖雪峰-正則表達式
正則表達式30分鐘入門教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末耀盗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子卦尊,更是在濱河造成了極大的恐慌叛拷,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岂却,死亡現(xiàn)場離奇詭異忿薇,居然都是意外死亡,警方通過查閱死者的電腦和手機躏哩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門署浩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人震庭,你說我怎么就攤上這事瑰抵∧愦疲” “怎么了器联?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長婿崭。 經常有香客問我拨拓,道長,這世上最難降的妖魔是什么氓栈? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任渣磷,我火速辦了婚禮,結果婚禮上授瘦,老公的妹妹穿的比我還像新娘醋界。我一直安慰自己,他們只是感情好提完,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布形纺。 她就那樣靜靜地躺著,像睡著了一般徒欣。 火紅的嫁衣襯著肌膚如雪逐样。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機與錄音脂新,去河邊找鬼挪捕。 笑死,一個胖子當著我的面吹牛争便,可吹牛的內容都是我干的级零。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼始花,長吁一口氣:“原來是場噩夢啊……” “哼妄讯!你這毒婦竟也來了?” 一聲冷哼從身側響起酷宵,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤亥贸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后浇垦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炕置,經...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年男韧,在試婚紗的時候發(fā)現(xiàn)自己被綠了朴摊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡此虑,死狀恐怖甚纲,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情朦前,我是刑警寧澤介杆,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站韭寸,受9級特大地震影響遇革,放射性物質發(fā)生泄漏氨距。R本人自食惡果不足惜门粪,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一铣焊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晶渠,春花似錦凰荚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至憨颠,卻和暖如春胳徽,著一層夾襖步出監(jiān)牢的瞬間积锅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工养盗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留缚陷,地道東北人。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓往核,卻偏偏與公主長得像箫爷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子聂儒,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內容