天天看點

Python正規表達式re子產品簡明筆記

簡介

正規表達式(regular expression)是可以比對文本片段的模式。最簡單的正規表達式就是普通字元串,可以比對其自身。比如,正規表達式 ‘hello’ 可以比對字元串 ‘hello’。

要注意的是,正規表達式并不是一個程式,而是用于處理字元串的一種模式,如果你想用它來處理字元串,就必須使用支援正規表達式的工具,比如 linux 中的 awk, sed, grep,或者程式設計語言 perl, python, java 等等。

正規表達式有多種不同的風格,下表列出了适用于 python 或 perl 等程式設計語言的部分元字元以及說明:

Python正規表達式re子產品簡明筆記

re 子產品

在 python 中,我們可以使用内置的 re 子產品來使用正規表達式。

有一點需要特别注意的是,正規表達式使用 <code>\</code> 對特殊字元進行轉義,比如,為了比對字元串 ‘python.org’,我們需要使用正規表達式 <code>'python\.org'</code>,而 python 的字元串本身也用 <code>\</code> 轉義,是以上面的正規表達式在 python 中應該寫成 <code>'python\\.org'</code>,這會很容易陷入 <code>\</code> 的困擾中,是以,我們建議使用 python 的原始字元串,隻需加一個 r 字首,上面的正規表達式可以寫成:

r'python\.org'

re 子產品提供了不少有用的函數,用以比對字元串,比如:

compile 函數

match 函數

search 函數

findall 函數

finditer 函數

split 函數

sub 函數

subn 函數

re 子產品的一般使用步驟如下:

使用 compile 函數将正規表達式的字元串形式編譯為一個 pattern 對象

通過 pattern 對象提供的一系列方法對文本進行比對查找,獲得比對結果(一個 match 對象)

最後使用 match 對象提供的屬性和方法獲得資訊,根據需要進行其他的操作

compile 函數用于編譯正規表達式,生成一個 pattern 對象,它的一般使用形式如下:

re.compile(pattern[, flag])

其中,pattern 是一個字元串形式的正規表達式,flag 是一個可選參數,表示比對模式,比如忽略大小寫,多行模式等。

下面,讓我們看看例子。

import re

# 将正規表達式編譯成 pattern 對象

pattern = re.compile(r'\d+')

在上面,我們已将一個正規表達式編譯成 pattern 對象,接下來,我們就可以利用 pattern 的一系列方法對文本進行比對查找了。pattern 對象的一些常用方法主要有:

match 方法

search 方法

findall 方法

finditer 方法

split 方法

sub 方法

subn 方法

match 方法用于查找字元串的頭部(也可以指定起始位置),它是一次比對,隻要找到了一個比對的結果就傳回,而不是查找所有比對的結果。它的一般使用形式如下:

match(string[, pos[, endpos]])

其中,string 是待比對的字元串,pos 和 endpos 是可選參數,指定字元串的起始和終點位置,預設值分别是 0 和 len (字元串長度)。是以,當你不指定 pos 和 endpos 時,match 方法預設比對字元串的頭部。

當比對成功時,傳回一個 match 對象,如果沒有比對上,則傳回 none。

看看例子。

&gt;&gt;&gt; import re

&gt;&gt;&gt; pattern = re.compile(r'\d+')                    # 用于比對至少一個數字

&gt;&gt;&gt; m = pattern.match('one12twothree34four')        # 查找頭部,沒有比對

&gt;&gt;&gt; print m

none

&gt;&gt;&gt; m = pattern.match('one12twothree34four', 2, 10) # 從'e'的位置開始比對,沒有比對

&gt;&gt;&gt; m = pattern.match('one12twothree34four', 3, 10) # 從'1'的位置開始比對,正好比對

&gt;&gt;&gt; print m                                         # 傳回一個 match 對象

&lt;_sre.sre_match object at 0x10a42aac0&gt;

&gt;&gt;&gt; m.group(0)   # 可省略 0

'12'

&gt;&gt;&gt; m.start(0)   # 可省略 0

3

&gt;&gt;&gt; m.end(0)     # 可省略 0

5

&gt;&gt;&gt; m.span(0)    # 可省略 0

(3, 5)

在上面,當比對成功時傳回一個 match 對象,其中:

<code>group([group1, …])</code> 方法用于獲得一個或多個分組比對的字元串,當要獲得整個比對的子串時,可直接使用 <code>group()</code> 或 <code>group(0)</code>;

<code>start([group])</code> 方法用于擷取分組比對的子串在整個字元串中的起始位置(子串第一個字元的索引),參數預設值為 0;

<code>end([group])</code> 方法用于擷取分組比對的子串在整個字元串中的結束位置(子串最後一個字元的索引+1),參數預設值為 0;

<code>span([group])</code> 方法傳回 <code>(start(group), end(group))</code>。

再看看一個例子:

&gt;&gt;&gt; pattern = re.compile(r'([a-z]+) ([a-z]+)', re.i)   # re.i 表示忽略大小寫

&gt;&gt;&gt; m = pattern.match('hello world wide web')

&gt;&gt;&gt; print m                               # 比對成功,傳回一個 match 對象

&lt;_sre.sre_match object at 0x10bea83e8&gt;

&gt;&gt;&gt; m.group(0)                            # 傳回比對成功的整個子串

'hello world'

&gt;&gt;&gt; m.span(0)                             # 傳回比對成功的整個子串的索引

(0, 11)

&gt;&gt;&gt; m.group(1)                            # 傳回第一個分組比對成功的子串

'hello'

&gt;&gt;&gt; m.span(1)                             # 傳回第一個分組比對成功的子串的索引

(0, 5)

&gt;&gt;&gt; m.group(2)                            # 傳回第二個分組比對成功的子串

'world'

&gt;&gt;&gt; m.span(2)                             # 傳回第二個分組比對成功的子串

(6, 11)

&gt;&gt;&gt; m.groups()                            # 等價于 (m.group(1), m.group(2), ...)

('hello', 'world')

&gt;&gt;&gt; m.group(3)                            # 不存在第三個分組

traceback (most recent call last):

  file "&lt;stdin&gt;", line 1, in &lt;module&gt; indexerror: no such group

search 方法用于查找字元串的任何位置,它也是一次比對,隻要找到了一個比對的結果就傳回,而不是查找所有比對的結果,它的一般使用形式如下:

search(string[, pos[, endpos]])

其中,string 是待比對的字元串,pos 和 endpos 是可選參數,指定字元串的起始和終點位置,預設值分别是 0 和 len (字元串長度)。

讓我們看看例子:

&gt;&gt;&gt; pattern = re.compile('\d+')

&gt;&gt;&gt; m = pattern.search('one12twothree34four')  # 這裡如果使用 match 方法則不比對

&gt;&gt;&gt; m

&lt;_sre.sre_match object at 0x10cc03ac0&gt;

&gt;&gt;&gt; m.group()

&gt;&gt;&gt; m = pattern.search('one12twothree34four', 10, 30)  # 指定字元串區間

&lt;_sre.sre_match object at 0x10cc03b28&gt;

'34'

&gt;&gt;&gt; m.span()

(13, 15)

再來看一個例子:

# -*- coding: utf-8 -*-

# 使用 search() 查找比對的子串,不存在比對的子串時将傳回 none

# 這裡使用 match() 無法成功比對

m = pattern.search('hello 123456 789')

if m:

    # 使用 match 獲得分組資訊

    print 'matching string:',m.group()

    print 'position:',m.span()

執行結果:

matching string: 123456

position: (6, 12)

上面的 match 和 search 方法都是一次比對,隻要找到了一個比對的結果就傳回。然而,在大多數時候,我們需要搜尋整個字元串,獲得所有比對的結果。

findall 方法的使用形式如下:

findall(string[, pos[, endpos]])

findall 以清單形式傳回全部能比對的子串,如果沒有比對,則傳回一個空清單。

看看例子:

pattern = re.compile(r'\d+')   # 查找數字

result1 = pattern.findall('hello 123456 789')

result2 = pattern.findall('one1two2three3four4', 0, 10)

print result1

print result2

['123456', '789']

['1', '2']

finditer 方法的行為跟 findall 的行為類似,也是搜尋整個字元串,獲得所有比對的結果。但它傳回一個順序通路每一個比對結果(match 對象)的疊代器。

result_iter1 = pattern.finditer('hello 123456 789')

result_iter2 = pattern.finditer('one1two2three3four4', 0, 10)

print type(result_iter1)

print type(result_iter2)

print 'result1...'

for m1 in result_iter1:   # m1 是 match 對象

    print 'matching string: {}, position: {}'.format(m1.group(), m1.span())

print 'result2...'

for m2 in result_iter2:

    print 'matching string: {}, position: {}'.format(m2.group(), m2.span())

&lt;type 'callable-iterator'&gt;

result1...

matching string: 123456, position: (6, 12)

matching string: 789, position: (13, 16)

result2...

matching string: 1, position: (3, 4)

matching string: 2, position: (7, 8)

split 方法按照能夠比對的子串将字元串分割後傳回清單,它的使用形式如下:

split(string[, maxsplit])

其中,maxsplit 用于指定最大分割次數,不指定将全部分割。

p = re.compile(r'[\s\,\;]+')

print p.split('a,b;; c   d')

['a', 'b', 'c', 'd']

sub 方法用于替換。它的使用形式如下:

sub(repl, string[, count])

其中,repl 可以是字元串也可以是一個函數:

如果 repl 是字元串,則會使用 repl 去替換字元串每一個比對的子串,并傳回替換後的字元串,另外,repl 還可以使用 <code>\id</code> 的形式來引用分組,但不能使用編号 0;

如果 repl 是函數,這個方法應當隻接受一個參數(match 對象),并傳回一個字元串用于替換(傳回的字元串中不能再引用分組)。

count 用于指定最多替換次數,不指定時全部替換。

p = re.compile(r'(\w+) (\w+)')

s = 'hello 123, hello 456'

def func(m):

    return 'hi' + ' ' + m.group(2)

print p.sub(r'hello world', s)  # 使用 'hello world' 替換 'hello 123' 和 'hello 456'

print p.sub(r'\2 \1', s)        # 引用分組

print p.sub(func, s)

print p.sub(func, s, 1)         # 最多替換一次

hello world, hello world

123 hello, 456 hello

hi 123, hi 456

hi 123, hello 456

subn 方法跟 sub 方法的行為類似,也用于替換。它的使用形式如下:

subn(repl, string[, count])

它傳回一個元組:

(sub(repl, string[, count]), 替換次數)

元組有兩個元素,第一個元素是使用 sub 方法的結果,第二個元素傳回原字元串被替換的次數。

print p.subn(r'hello world', s)

print p.subn(r'\2 \1', s)

print p.subn(func, s)

print p.subn(func, s, 1)

('hello world, hello world', 2)

('123 hello, 456 hello', 2)

('hi 123, hi 456', 2)

('hi 123, hello 456', 1)

其他函數

事實上,使用 compile 函數生成的 pattern 對象的一系列方法跟 re 子產品的多數函數是對應的,但在使用上有細微差别。

match 函數的使用形式如下:

re.match(pattern, string[, flags]):

其中,pattern 是正規表達式的字元串形式,比如 <code>\d+</code>, <code>[a-z]+</code>。

而 pattern 對象的 match 方法使用形式是:

可以看到,match 函數不能指定字元串的區間,它隻能搜尋頭部,看看例子:

m1 = re.match(r'\d+', 'one12twothree34four')

if m1:

    print 'matching string:',m1.group()

else:

    print 'm1 is:',m1

m2 = re.match(r'\d+', '12twothree34four')

if m2:

    print 'matching string:', m2.group()

    print 'm2 is:',m2

m1 is: none

matching string: 12

search 函數的使用形式如下:

re.search(pattern, string[, flags])

search 函數不能指定字元串的搜尋區間,用法跟 pattern 對象的 search 方法類似。

findall 函數的使用形式如下:

re.findall(pattern, string[, flags])

findall 函數不能指定字元串的搜尋區間,用法跟 pattern 對象的 findall 方法類似。

print re.findall(r'\d+', 'hello 12345 789')

# 輸出

['12345', '789']

finditer 函數的使用方法跟 pattern 的 finditer 方法類似,形式如下:

re.finditer(pattern, string[, flags])

split 函數的使用形式如下:

re.split(pattern, string[, maxsplit])

sub 函數的使用形式如下:

re.sub(pattern, repl, string[, count])

subn 函數的使用形式如下:

re.subn(pattern, repl, string[, count])

到底用哪種方式

從上文可以看到,使用 re 子產品有兩種方式:

使用 re.compile 函數生成一個 pattern 對象,然後使用 pattern 對象的一系列方法對文本進行比對查找;

直接使用 re.match, re.search 和 re.findall 等函數直接對文本比對查找;

下面,我們用一個例子展示這兩種方法。

先看第 1 種用法:

# 将正規表達式先編譯成 pattern 對象

print pattern.match('123, 123')

print pattern.search('234, 234')

print pattern.findall('345, 345')

再看第 2 種用法:

print re.match(r'\d+', '123, 123')

print re.search(r'\d+', '234, 234')

print re.findall(r'\d+', '345, 345')

如果一個正規表達式需要用到多次(比如上面的 <code>\d+</code>),在多種場合經常需要被用到,出于效率的考慮,我們應該預先編譯該正規表達式,生成一個

pattern 對象,再使用該對象的一系列方法對需要比對的檔案進行比對;而如果直接使用 re.match, re.search

等函數,每次傳入一個正規表達式,它都會被編譯一次,效率就會大打折扣。

是以,我們推薦使用第 1 種用法。

比對中文

假設現在想把字元串 <code>title = u'你好,hello,世界'</code> 中的中文提取出來,可以這麼做:

title = u'你好,hello,世界'

pattern = re.compile(ur'[\u4e00-\u9fa5]+')

result = pattern.findall(title)

print result

注意到,我們在正規表達式前面加上了兩個字首 <code>ur</code>,其中 <code>r</code> 表示使用原始字元串,<code>u</code> 表示是 unicode 字元串。

執行結果:

[u'\u4f60\u597d', u'\u4e16\u754c']

貪婪比對

在 python 中,正則比對預設是貪婪比對(在少數語言中可能是非貪婪),也就是比對盡可能多的字元。

比如,我們想找出字元串中的所有 <code>div</code> 塊:

content = 'aa&lt;div&gt;test1&lt;/div&gt;bb&lt;div&gt;test2&lt;/div&gt;cc'

pattern = re.compile(r'&lt;div&gt;.*&lt;/div&gt;')

result = pattern.findall(content)

['&lt;div&gt;test1&lt;/div&gt;bb&lt;div&gt;test2&lt;/div&gt;']

由于正則比對是貪婪比對,也就是盡可能多的比對,是以,在成功比對到第一個 <code>&lt;/div&gt;</code> 時,它還會向右嘗試比對,檢視是否還有更長的可以成功比對的子串。

如果我們想非貪婪比對,可以加一個 <code>?</code>,如下:

pattern = re.compile(r'&lt;div&gt;.*?&lt;/div&gt;')    # 加上 ?

結果:

['&lt;div&gt;test1&lt;/div&gt;', '&lt;div&gt;test2&lt;/div&gt;']

小結

使用 compile 函數将正規表達式的字元串形式編譯為一個 pattern 對象;

通過 pattern 對象提供的一系列方法對文本進行比對查找,獲得比對結果(一個 match 對象);

最後使用 match 對象提供的屬性和方法獲得資訊,根據需要進行其他的操作;

python 的正則比對預設是貪婪比對。

作者:funhacks

來源:51cto