為什么要用正則表達式
對字符串進行操作幾乎是每種編程語言中最重要的功能之一。很簡單就可以理解,因為人類進行信息傳播主要靠的是文字,也就是字符串,但是這么多信息并不完全是我們所要的涎嚼,所以我們會通過編程來提取或者驗證字符串的部分。
正則表達式就是用來匹配字符串的工具挑秉,其實它定義了一套語法法梯,用若干描述字符就可以匹配出某段字符串的特征來。凡是符合種描述規(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));
例子:提取日期
下面我們希望能自動化的把一段文字中的生日
給提取出來,但是如果之前沒有規(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ù)字部分只可能有01
和1
兩種形式:\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));
編譯
使用正則表達式時灼捂,re模塊內部會干兩件事情:
- 編譯正則表達式离例,此時會進行語法分析,如果表達式本身不合法悉稠,會報錯宫蛆;
- 用編譯后的正則表達式去匹配字符串。
那么如果一個正則表達式要使用非常多次的猛,可以預編譯該正則表達式
# 編譯:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')