1. 程式人生 > 實用技巧 >正則表示式詳解(下)

正則表示式詳解(下)

書接上文,在下篇中主要介紹如何在 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)

。第一次解讀,python 將其理解為\\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 的《精通正則表示式》,裡面有非常專業的解釋,相信它可以幫你解決正則表示式的絕大部分疑問。