文章目錄
正則表達式與Python語言
. Python當前通過使用re模塊來支持正則表達式,這裏將介紹兩個主要的函數——match()和serarch(),以及compile()函數。其他常用函數/方法還有:
使用compile()函數編譯正則表達式
. compile函數的形式如:compile(pattern,flags = 0),它表示使用任何可選的標記來編譯正則表達式的模式,然後返回一個正則表達式對象。
推薦使用預編譯的原因是調用一個代碼對象往往比調用一個字符串的性能要高,因爲對前者而言,編譯過程不會重複執行,在正則表達式的執行過程中通常將進行多次比較操作,因此強烈建議使用預編譯。
值得注意的是,儘管推薦使用預編譯,但是它並不是必需的,如果需要編譯,就使用編譯過的方法;如果不需要編譯,就使用函數。
對於一些特殊的正則表達式的編譯,可選的標記可能以參數的形式給出,即flags的值可以使用其他值,常用的標記如下:
匹配對象以及group()和groups()方法
. 匹配對象是成功調用match()或search()後返回的對象。其有兩個主要的方法:group()和groups()。
group要麼返回整個匹配對象,要麼根據要求返回特定的子組;groups則返回一個包含唯一或全部子組的元祖。
使用match()方法匹配字符串
. match()函數試圖從字符串的起始部分對模式進行匹配,如果匹配成功就返回一個匹配對象,如果匹配失敗,就返回None,匹配對象的group方法能夠用於顯示那個成功的匹配。例如:
>>> import re
>>> m = re.match('foo','foo')
>>> m.group()
'foo'
>>> n = re.match('foo','bar')
>>> n.group()
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
n.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>>
使用search()在一個字符串中查找模式
. 通常想要搜索的模式出現在字符串的中間部分的概率要比出現在起始部分的概率要大,這就是search派上用處的原因。search的工作方式和match的工作方式完全相同,不同的地方在於search()會用它的字符串參數,在任意位置對給定正則表達式模式搜索第一次出現的情況。如果成功匹配九返回一個匹配對象,匹配失敗就返回None。以下是兩者的區別:
>>> m = re.match('foo','seafood')
>>> m.group()
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
m.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> n = re.search('foo','seafood')
>>> n.group()
'foo'
>>>
匹配多個字符串
. 即擇一匹配符號(|)的使用:
>>> bt = 'bat|bet|bit' #正則表達式模式
>>> m = re.match (bt,'bat')
>>> m.group()
'bat'
>>> m = re.match(bt,'he bit me')
>>> m
>>> m.group()
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
m.group()
AttributeError: 'NoneType' object has no attribute 'group'
>>> m = re.search(bt,'he bit me')
>>> m
<_sre.SRE_Match object; span=(3, 6), match='bit'>
>>> m.group()
'bit'
. 其他的類似點號(.),創建字符集([])的使用與擇一匹配符形式差不多。
分組符號
. 之前講group和groups可以用來訪問子組,那用來匹配和保存子組的符號就是一對圓括號:
>>> a = '(\w\w\w)-(\d\d\d)'
>>> m = re.match(a,'asd-123')
>>> m.groups()
('asd', '123')
>>> m.group(1)
'asd'
>>> m.group(2)
'123'
>>> m.group()
'asd-123'
使用findall()和finditer()查找每一次出現的位置
. findall查詢字符串中某個正則表達式模式全部的非重複出現情況。這與search()在執行的時候類似,但不同的地方在於,findall返回的是一個列表,如果沒有找到匹配的部分,就返回一個空列表,但如果匹配成功,列表將包含所有成功的匹配部分(從左往右按順序排列)。
>>> re.findall('car','carry the barcardi to the car')
['car', 'car', 'car']
. 這還是挺有用的,如果模式複雜一點,比如郵箱啊,電話呀,可以在後面的內容中都查找出來。
finditer()函數是在Python2.2版本中添加回來的,這是一個與findall()函數類似但是更節省內存的變體。其和findall以及其他變體函數之間的差異在於finditer()在匹配對象中迭代:
>>> s = 'This and that'
>>> re.findall(r'(th\w+) and (th\w+)',s,re.I)
[('This', 'that')]
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I)
<callable_iterator object at 0x0000000002F48898>
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I).__next__()
<_sre.SRE_Match object; span=(0, 13), match='This and that'>
>>> re.finditer(r'(th\w+) and (th\w+)',s,re.I).__next__().groups()
('This', 'that')
>>> [g.groups() for g in re.finditer(r'(th\w+)',s,re.I)]
[('This',), ('that',)]
使用sub()和subn()搜索和替換
. 有兩個函數用於搜索和替換:sub()和subn()。兩者幾乎一模一樣,都是將某字符串中所有匹配正則表達式的部分進行替換,最大的不同在於subn()還返回一個替換的總數:
>>> print(re.sub('X','Mr.Smith','attn: X\n\nDear X\n'))
attn: Mr.Smith
Dear Mr.Smith
>>> re.subn('[ae]','x','abcdefg')
('xbcdxfg', 2)
. 使用匹配對象的 group()方法除了能夠取出匹配分組編號外,還可以使用\N,其中 N 是在替換字符串中使用的分組編號:
>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2})',r'\2/\1/\3','2/20/91')
'20/2/91'
. 第二個參數表示將第二子組和第一子組做一個調換。
在限定模式上使用split()分割字符串
. split()基於正則表達式的模式分隔字符串,爲字符串分隔功能添加
一些額外的威力。例如,一個用於 Web 站點的簡單解析器,用戶需要輸入城市和州名,或者城市名加上 ZIP 編碼,還是三者同時輸入?
>>> import re
>>> DATA = (
'Mountain View, CA 94040',
'Sunnyvale, CA',
'Los Altos, 94023',
'Cupertino 95014',
'Palo Alto CA',
)
>>> for datum in DATA:
print(re.split(', |(?= (?:\d{5}|[A-Z]{2})) ', datum))
['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']
. 要注意的是正則表達式模式中最後是有一個空格的,表示如果空格緊跟在五個數字或者兩個大寫字母(美國聯邦州縮寫)之後,就用 split 語句分割該空格。這就允許在城市名中放置空格。
擴展符號
re.I/IGNORECASE
. 表示對匹配的模式不區分大小寫:
>>> re.findall(r'(?i)yes', 'yes? Yes. YES!!')
['yes', 'Yes', 'YES']
re.S/DOTALL
. 表示表明點號(.)能夠用來表示\n 符號:
>>> re.findall(r'(?s)th.+', '''
The first line
the second line
the third line
''')
['the second line\n\tthe third line\n']
re.X/VERBOSE
. 這個標記可將正則表達式模式寫成多行,並且忽略掉空格,也允許添加註釋,這樣就能使得正則表達式更加易讀:
>>> re.search(r'''(?x)
\((\d{3})\) # 區號
[ ] # 空白符
(\d{3}) # 前綴
- # 橫線
(\d{4}) # 終點數字
''', '(800) 555-1212').groups()
('800', '555', '1212')
. 上面有個空白符,不是指的忽略這個,而是說忽略字符到#(註釋)之間的空白。
(?: )
. 通過使用該符號,可以對部分正則表達式進行分組,但是並不會保存該分組用於後續的檢索或者應用。當不想保存今後永遠不會使用的多餘匹配時,這個符號就非常有用。
(?P<name>) 和 (?P=name)
. 前者通過使用一個名稱標識符而不是使用從 1 開始增加到 N 的增量數字來保存匹配:
>>> re.sub(r'\((?P<a>\d{3})\) (?P<b>\d{3})-(?:\d{4})','(\g<a>) \g<b>-xxxx','(800) 555-1212')
'(800) 555-xxxx'
. 上面的sub函數的作用是替換匹配正則表達式的字符串。
使用後者,可以在一個相同的正則表達式中重用模式,而不必稍後再次在相同的正則表達式中指定相同的模式,例如:
>>> bool(re.match(r'''(?x)
\((?P<num1>\d{3})\)[ ](?P<num2>\d{3})-(?P<num3>\d{4})
[ ]
(?P=num1)-(?P=num2)-(?P=num3)
[ ]
1(?P=num1)(?P=num2)(?P=num3)''','(800) 555-1212 800-555-1212 18005551212'))
True
(?=…) 和 (?!..)
. 可以在目標字符串中實現一個前視匹配,而不必實際上使用這些字符串。前者是正向前視斷言,後者是負向前視斷言。例如假設只對姓氏爲“Tom”的人感興趣:
>>> re.findall(r'\w+(?= Tom)','''
Guido Tom
Tim Peters
Alex Martelli
Mike Tom
Raymond Hettinger''')
['Guido', 'Mike']
. 另一個示例中假設忽略以“tom”和“jerry”開頭的email地址:
>>> re.findall(r'(?m)^\s+(?!tom|jerry)(\w+)','''
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]''')
['sales', 'eng', 'admin']