正則表示式詳解(下)
書接上文,在下篇中主要介紹如何在 python 中使用正則表示式實現文字的匹配和替換工作。
匹配字串
python 的 re 模組支援正則表示式,基本步驟是使用 compile 編譯正則表示式為 pattern 例項,之後使用例項匹配目標文字(一般使用方法 match 和 search)。先來看一個簡單的例子,
# encoding: UTF-8 import re string = "Tutorial. 0123456789 _ + -., !@ # $%^&*();\/|<>\"\'12345 - 98.7 3.141 .6180 9,000 + 42 55.123.4567 + 1 - (800) - 555 - 2468 [email protected] [email protected] www.demo.com" pattern = re.compile(r"\d+", re.M) sear = re.search(pattern, string) print('sear', sear) mat = re.match(pattern, string) print('mat', mat) fin = re.findall(pattern, string) print('fin', fin)
程式碼第 4 行,使用正則表示式r"\d+"
compile 編譯一個 pattern 的例項。
為什麼要在正則表示式之前加個
r
呢?
此處的r
表示 raw,即原始輸入。因為\
在 python 的字串中也是轉義字元,此處的功能與正則表示式中的功能有衝突。compile
中的表示式從輸入到真正的表示式的意思執行需要經過兩次解讀,python 本身解讀之後將字串表達的意思傳遞給正則表示式,正則引擎再解讀之後執行,問題就出在兩次解讀上。
以最終匹配\section
為例,如果不加r
使用 python 第 5 行的引數就該是\\\\section
,即pattern = re.compile("\\\\section", re.M)
\\section
傳遞給正則引擎,正則理解為要匹配\section
,如下表所示
Characters | Stage |
---|---|
\section |
希望匹配的字串 |
\\section |
正則引擎新增反斜線 re.compile() |
"\\\\section" |
python 字串為表示\ 字元再新增反斜線 |
所以為了匹配 1 個\
最後需要寫 4 個\
,實在麻煩,為了便利,因此使用r在字串之前,表示去掉 Python 的解讀,僅保留正則引擎的解讀。比如下表中的引數寫法就能省掉很多不必要的\
,十分方便。
Regular String | Raw string |
---|---|
"ab*" |
r"ab*" |
"\\section" |
r"\section" |
"\\w+\\s+\\1" |
r"\w+\s+\1" |
re.M
是個什麼東東?
上面的程式碼中,compile 還包含了re.M
作為一個 flag,它是MULTILINE
的縮寫,表示匹配多行模式,除了多行模式之外(具體含義見下表),python 還有其他的 flag 影響表示式匹配的規則。
Flag | Meaning |
---|---|
ASCII , A |
使得 \w , \b , \s 和 \d 僅僅匹配 ASCII 文字 |
DOTALL , S |
使得 . 匹配換行符在內的所有字元 |
IGNORECASE , I |
忽略大小寫 |
LOCALE , L |
使用 locale 匹配 |
MULTILINE , M |
多行匹配,影響 ^ 和 $ |
VERBOSE , X (for ‘extended’) |
忽略表示式中的空格,允許插入註釋 |
需要說明的是X
模式,使用方法如下
charref = re.compile(r"""
&[#] # Start of a numeric entity reference
(
0[0-7]+ # Octal form
| [0-9]+ # Decimal form
| x[0-9a-fA-F]+ # Hexadecimal form
)
; # Trailing semicolon
""", re.VERBOSE)
表示式內部加入了註釋,而且可以多行顯示,可以使用它表示複雜的表示式,非常方便。
匹配正則表示式的方法
pattern 的物件建立之後,就是可以使用 re 的方法匹配字串了。最重要的方法有下表中的四個。
方法或者屬性 | 目的 |
---|---|
match() |
表示式是否匹配字串的開頭 |
search() |
瀏覽整個字串,匹配其中任意位置 |
findall() |
匹配所有的字元子串,返回列表 |
finditer() |
匹配所有的字元子串,返回 iterator. |
執行上面的程式碼如下所示
>>>
sear <_sre.SRE_Match object; span=(11, 21), match='0123456789'>
mat None
fin ['0123456789', '12345', '98', '7', '3', '141', '6180', '9', '000', '42', '55', '123', '4567', '1', '800', '555', '2468']
sear 匹配成功,返回匹配的物件。mat 匹配失敗,fin 匹配成功並且返回了字元列表。
匹配成功之後物件有如下方法
方法 | 目的 |
---|---|
group() |
返回匹配的字串 |
start() |
返回匹配字串的開始位置 |
end() |
返回匹配字串的結束位置 |
span() |
返回匹配字串開始位置和結束位置的元組 (start, end) |
group()
在上篇中講過,它在字元捕獲時候非常有用,字元的分組從 0 開始,其中第 0 個代表所有匹配的內容,1 代表匹配的第一組,以此類推。比如下面的程式碼
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
可以將group(0)
理解成沒有分組符號匹配的所有字元,而之後的編號代表分組依次匹配的子串。
修改字串
模組級別的字串修改方法如下表所示。
方法和屬性 | 目的 |
---|---|
split() |
使用 pattern 將目標字串分割,並返回列表 |
sub() |
查詢所有匹配的子串,並替換之,返回替換之後的字串 |
subn() |
查詢所有匹配的子串,並替換之,返回替換之後的字串和次數 |
.split
(string[, maxsplit=0]) 將字串分割,其中有引數最大分塊,預設將分割成最多的塊。
>>> p = re.compile(r'\W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
替換字串
.sub
(replacement, string[, count=0]) 將 string 中所有符合 pattern 模式替換成 replacement,count 表示替換的次數。看如下的例子
>>> p = re.compile( '(blue|white|red)')
>>> p.sub( 'colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub( 'colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
方法subn()
與sub()
類似,但是它返回一個元組,其中包含替換之後的字串,以及替換的次數,下面是另一個例子
>>> p = re.compile( '(blue|white|red)')
>>> p.subn( 'colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn( 'colour', 'no colours at all')
('no colours at all', 0)
特別地,替換可以使用分組,這在交換字串的位置時非常有用。舉例如下
>>> strin = 'This is part1 and part2'
>>> p = re.compile('(.*)(part1)(.*)(part2)')
>>> p.sub(r'\1\4\3\2', strin)
'This is part2 and part1'
我們還可以使用之前的名字分組,除了使用\number
指代第幾個分組之外,還可以使用\g<number>
指代已經命名的分組。一個典型的例子如下所示
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE)
>>> p.sub(r'subsection{\1}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<1>}','section{First}')
'subsection{First}'
>>> p.sub(r'subsection{\g<name>}','section{First}')
'subsection{First}'
使用正則表示式
考慮使用正則表示式之前,先想想是否可以使用字串方法就能解決問題。如果僅僅匹配和替換固定字串,python 自帶的字串方法速度更快,效率更高,不是每個字串的替換都需要使用正則表示式。
未盡事宜
相信以上介紹的內容能夠解決大部分的字元處理問題,但是對於專業大資料處理可能還需要了解正則表示式更多的內容,比如不同語言的正則表示式語法,以及複雜正則表示式的匹配效率。這些比較深入的知識可以參考 Jeffrey E.F. Friedl 的《精通正則表示式》,裡面有非常專業的解釋,相信它可以幫你解決正則表示式的絕大部分疑問。