1. 程式人生 > >正則表示式與Python(二更 2018 3.7 18:07)

正則表示式與Python(二更 2018 3.7 18:07)

正則表示式

自己去網上查,這裡不教。

re模組:核心函式和方法

使用compile()函式編譯正則表示式

使用任何可選的標記來編譯正則表示式的模式,然後返回一個正則表示式物件。

推薦預編譯,但並不是必須的。

如果需要編譯,就是用編譯後的方法

如果不需要編譯,就使用函式

幸運的是,無論是使用函式還是使用方法,它們的名字都是相同的。

可選標記

重用的模組屬性:

屬性(標記) 作用
re.I re.IGNORECASE 不區分大小寫的匹配
re.L re.LOCAL 根據所使用的本地語言環境通過\w、\W、\b、\B、\s、\S實現匹配
re.M re.MULTILINE ^和$分別匹配目標字串中行的起始和末尾,而不是嚴格匹配整個字串本身的起始和末尾
re.S re.DOTALL “.”(點號)通常匹配除了\n(換行符)之外的所有單個字元;該標記表示“.”(點號)能夠匹配全部字元
re.X re.VERBOSE 該標誌通過給予你更靈活的格式以便你將正則表示式寫得更易於理解。當該標誌被指定時,在 RE 字串中的空白符被忽略,除非該空白符在字元類中或在反斜槓之後;這可以讓你更清晰地組織和縮排 RE。它也可以允許你將註釋寫入 RE,這些註釋會被引擎忽略;註釋用 “#”號 來標識,不過該符號不能在字串或反斜槓之後。

關於這些標記後面會詳細解釋。

匹配物件以及group()和groups()方法

除了正則表示式物件,還有一個物件型別:匹配物件。這些是成功呼叫match()或者search()返回的物件。

匹配物件有兩個主要的方法:group()groups()

group(num=0)

要麼返回整個匹配物件,或者編號為num的子組。

groups(default=None)

返回一個包含所有匹配子組的元組(如果沒有成功匹配,則返回一個空元組)

groupdict(default=None)

返回一個包含所有匹配的命名子組的字典,所有的子組名稱為鍵(匹配失敗,返回空字典)

使用match(pattern, string, flags=0)方法匹配字串

嘗試使用帶有可選的標記的正則表示式的模式來匹配字串。如果匹配成功,就返回匹配物件;如果失敗,則返回None。

運用match()group()的例項:

import re

m = re.match('foo', 'foo')
if m:
    print(m.group())

輸出:

foo

if語句避免了匹配失敗後丟擲的AttributeError異常。

使用search()在一個字串中查詢模式(搜尋與匹配的對比)

工作方式和match()完全一致,不同之處在於search()會用它的字串引數,在任意位置對給定正則表示式模式搜尋第一次出現的匹配情況。

例:

# 匹配失敗
m = re.match('foo', 'afoo') 

# 搜尋成功
m = re.search('foo', 'afoo') 

分組

直接上示例:

import re

patt = '(\w\w\w)-(\d\d\d)'
m = re.match(patt, 'abc-123')
if m:'
    print(m.group())
    print(m.group(1))
    print(m.group(2))

輸出:

abc-123
abc
123

可見,group()通常用於以普通方式顯示所有的匹配部分,但也能用於獲取各個匹配的子組。可以使用groups()方法來獲取一個包含所有匹配子字串的元組。

而且,要注意,用group()獲取子組時,數字從1開始。

原始字串

反斜線有轉義的功能,\n表示換行符,如果列印一個路徑,例如:

>>> print 'C:\nowhere'
# 列印結果如下
C:
owhere
# 我們可以通過反斜線進行轉義,改為:
>>> print 'C:\\nowhere'

複製程式碼
但是如果對於長路徑,那麼需要很多的反斜線,這樣原始字串就派上用場了。

用法:在字串前加上字母r

原始字元不會把反斜線當作特殊字串。

>>> print(r'C:\nowhere')
C:\nowhere

>>> print(r'C:\Program Files\fnord\foo\bar\baz\frozz\bozz')
C:\Program Files\fnord\foo\bar\baz\frozz\bozz

當然我們也要像平常一樣對引號進行轉義,但是,最後的輸出的字串也包含了轉義所用的反斜線

>>> print(r'Let's go)
SyntaxError: invalid syntax
>>> print(r'Let\'s go')
Let\'s go

但是不能在原始字串結尾輸入反斜線。

print(r"This is illegal\")

上面寫法會報錯,參照上一個範例這是一個顯而易見的結論。最後一個字元是反斜線,Python就不清楚是否應該結束字串。

但如果字串最後一個字元確實是\,可以使用一個技巧解決上述問題

>>> print(r'C:\Program Files\foo\bar' '\\')

C:\Program Files\foo\bar\

findall(pattern, string, flags=0)

查詢字串中所有(非重複)出現的正則表示式模。如果匹配成功,則返回匹配物件;如果失敗,則返回None。

finditer(pattern, string, flags=0)

與findall()相同,但返回一個迭代器,對於每一次匹配,迭代器都返回一個匹配物件。

使用findall()和finditer()查詢每一次出現的位置

>>> re.findall('foo', 'foo')
['foo']
>>> re.findall('foo', '?foo')
['foo']
>>> re.findall('foo', '?foo!foo#foo')
['foo', 'foo', 'foo']

當使用元組時,對於每一個成功的匹配,每個子組匹配是由findall()返回的結果列表中的單一元素;

對於多個成功的匹配(即有多個子組時),每個子組匹配使返回的一個元組中的單一元素,而且每個元組(每個元組都對應一個成功的匹配)是結果列表的元素。

通過示例來加強理解吧:

>>> s = 'This and that.'
>>> re.findall(r'(th\w+) and (th\w+)', s, re.I)
[('This', 'that')]

>>> for match in re.finditer(r'(th\w+) and (th\w+)', s, re.I):
        print(match.group())
This and that

簡單的說吧,就是finditer返回了一個可呼叫的物件,使用 for i in finditer()的形式,可以一個一個的得到匹配返回的匹配物件。這在對每次返回的物件進行比較複雜的操作時比較有用。

更多例子:

import re

s = 'This and that. These and those.'
result = re.finditer(r'(th\w+) and (th\w+)', s, re.I)

for match in result:
    print(match.group())

輸出:

This and that
These and those

至於匹配物件的操作(group()groups()之類),就是之前的知識了,這裡主要理解finditer()函式返回的迭代器是什麼意思。

高階操作:

import re

s = 'This and that. These and those.'
result = [r.groups() for r in re.finditer(r'(th\w+) and (th\w+)', s, re.I)]

print(result)

輸出:

[('This', 'that'), ('These', 'those')]

使用sub()和subn()搜尋與替換

兩者幾乎一樣,都是將某字串中所有匹配正則表示式的部分進行某種形式的替換。用來替換的部分通常是一個字串。

不同的是,subn()還返回一個表示替換總數的數字,和替換後的字串一起組成一個有兩個元素的元組返回。

例:

>>> re.sub(r'\d', '!', 'asdf1ghj2p')
asdf!ghj!p

>>> re.subn(r'\d', '!', 'asdf1ghj2p')
('asdf!ghj!p', 2)

分組(進階)

分組時,還可以使用\N,其中N是在替換字串中使用的分組編號,從1開始的數字。

從左到右,第一個分組的編號是1,第二個是2,以此類推。

舉例加以理解:

將美式的日期表示法:MM/DD/YY換成DD/MM/YY

>>> re.sub(r'(\d{1,2})/(\d{1,2})/(\d{2}|\d{4})', r'\2/\1/\3', '1/12/1998')
12/1/1998

一共分了三個組,從左到右,從1開始依次編號。

在限定模式上使用split(pattern, string, max=0)分隔字串

基於正則表示式的模式分隔字串,為字串分隔功能新增一些額外的能力。

若不想為每次模式的出現都分割字串,就可以通過為max引數設定一個非零值來指定最大分割數。

對於不需要使用特殊符號或特殊條件來分割的字串,re.split()str.split()工作方式相同。

>>> re.split(',' ,'1,2,3')
['1', '2', '3']

但如果情況更復雜的話,如,一個用於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(r', | (?=(?:[A-Z]{2}|\d{5}))', datum))

輸出:

['Mountain View', 'CA', '94040']
['Sunnyvale', 'CA']
['Los Altos', '94023']
['Cupertino', '95014']
['Palo Alto', 'CA']

這裡運用了正向前視斷言(?=),後面會詳細的講。

解釋起來就是:如果一個空格後面緊挨著2個大寫字母或者5個數字的話,就可以作為分隔符。

秀不秀?

這裡寫圖片描述

擴充套件符號

通過使用(?Lmsux)系列選項,使用者可以直接在正則表示式裡面指定一個或多個標記,而不是通過compile()或者其他re模組函式。

之前我們指定標記時用的是引數的方法,這次可以直接寫在正則表示式裡面。

之前的re.I/IGNORECASE等,只要把那個簡寫版的大寫字母變成小寫字母寫成這樣(?i)如果有多個標記就都寫在一起(?im)

接下來將展示各個標記的用法及示例。

re.I/IGNORECASE

>>> result = re.findall(r'(?i)OK', 'Ok, oK, ok, OK.')
['Ok', 'oK', 'ok', 'OK']

re.M/MULTILINE

import re


result = re.findall(r'(?im)(^th[\w ]+)', """
This line is the first,
another line ,
that line is the last.""")
print(result)

輸出:

['This line is the first', 'that line is the last']

通過使用“多行”標記,能夠在目標字串中跨行匹配,而不是視整個字串為整體。

比如,此時忽略了“the”,因為它不在行首。

re.S/DOTALL

該標記表明(.)能夠用來表示\n符號:

不用標記時:

import re

result = re.findall(r'th.+', """
the first line
the second line
the third line""")
print(result)

輸出:

['the first line', 'the second line', 'the third line']

使用標記時:

import re


result = re.findall(r'(?s)th.+', """
the first line
the second line
the third line""")
print(result)

輸出:

['the first line\nthe second line\nthe third line']

re.X/VERBOSE

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)

b = re.compile(r"\d+\.\d*")

第一個例子是用了re.VERBOSE標誌的,有了註釋,淺顯易懂。

第二個例子裡,Python 的字串自動連線可以用來將 RE 分成更小的部分,但它比用 re.VERBOSE 標誌時更難懂。

(?: )

通過使用該符號,可以對正則表示式進行分組,但是並不會儲存該分組用於後續的檢索或應用。

當不想儲存今後永遠不會使用的多餘匹配時,這個符號就非常有用。

import re

result = re.findall(r'http://(?:\w+\.)*(\w+\.com)', "http://www.google.com"
                                                    " http://google.com "
                                                    "http://code.google.com")
print(result)

輸出:

['google.com', 'google.com', 'google.com']

第一個分組只是為了方便後面的“*”而已,不需做儲存,所以用(?:)

(?P<name>)

通過一個名稱識別符號而不是使用從1開始增加到N的增量數字來儲存匹配。

>>> re.search(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?:\d{4})',
                   '(800) 555-1212').groupdict()
{'areacode': '800', 'prefix': '555'}

>>> re.search(r'\((\d{3})\) (\d{3})-(?:\d{4})',
                   '(800) 555-1212').groupdict()
{}

我們最後用了一個groupdict()將匹配結果轉換為字典,每個鍵就是每個組的名稱。

如果沒有指定名稱輸出就是空字典。

之前說過分組後可以使用\N來檢索,命名後可以使用\g<name>的形式來檢索:

>>> re.sub(r'\((?P<areacode>\d{3})\) (?P<prefix>\d{3})-(?:\d{4})',
                '(\g<areacode>) \g<prefix>-xxxx'
                ,'(800) 555-1212')
                n
'(800) 555-xxxx'