Python面試題之Python正則表達式re模塊


 

一、Python正則表達式re模塊簡介

正則表達式,是一門相對通用的語言。簡單說就是:用一系列的規則語法,去匹配,查找,替換等操作字符串,以達到對應的目的;此套規則,就是所謂的正則表達式。各個語言都有各自正則表達式的內置模塊,包括Linux系統中sed、awk也都是使用正則表達式。當然Python中也有對正則表達式的支持,對應的就是Python內置的re模塊。

Python的re模塊(Regular Expression,正則表達式)提供各種正則表達式的匹配操作,使用這一內嵌於Python的語言工具,盡管不能滿足所有復雜的匹配情況,但足夠在絕大多數情況下能夠有效地實現對復雜字符串的分析並提取出相關信息。Python會將正則表達式轉化為字節碼,利用C語言的匹配引擎進行深度優先的匹配。

 

二、正則表達式(Regexp)

正則表達式是由普通字符(例如字符a到z)以及特殊字符(稱為”元字符”)組成的文字模式。模式描述在搜索文本時要匹配的一個或多個字符串。正則表達式作為一個模板,將某個字符模式與所搜索的字符串進行匹配。

  • 普通字符

普通字符包括沒有顯式指定為元字符的所有可打印和不可打印字符。這包括所有大寫和小寫字母、所有數字、所有標點符號和一些其他符號。

  • 特殊字符

所謂特殊字符,就是一些有特殊含義的字符,如tes*t中的*,簡單的說就是表示任何字符串的意思。如果要查找字符串中的*符號,則需要對*進行轉義,即在其前加一個\,如tes\*t匹配tes*t。許多元字符要求在試圖匹配它們時特別對待,若要匹配這些特殊字符,必須首先使字符”轉義”,即,將反斜杠字符\放在它們前面。

  • 限定符

限定符用來指定正則表達式的一個給定組件必須要出現多少次才能滿足匹配。常用有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 等等。

  • 定位符

定位符用來描述字符串或單詞的邊界,^和$分別指字符串的開始與結束,\b描述單詞的前或后邊界,\B表示非單詞邊界。

下面表格列出了常用的正則表達式符號並給出了說明:

符號

說明

例子

.,.*

“.”表示任意字符,除換行符’\n’外。如果說指定了DOTALL的標識,就表示包括新行在內的所有字符。而“.*”表示任意長度任意字符

‘.est’ 可以匹配‘test’但不能匹配‘tooltest’,但‘.*est’可以匹配‘test’和‘tooltest’。

^

匹配以某某字符串開頭

‘test’可以匹配‘test’和‘tooltest’,但‘^test$’只能匹配‘test’。

$

匹配以某某字符串結尾

‘test’可以匹配‘test’和‘testtool’,但‘test$’只能匹配‘test’。

*,+,?

‘*’表示后面可跟0個或多個字符,’+’表示后面可跟1個或多個字符,’?’表示后面可跟0個或多個字符

正則表達式’ab*’如果用於查找’abbbc’,將找到’abbb’。’ab?’將找到’ab’;而如果使用非貪婪的數量詞’ab*?’,將找到’a’。

*?,+?,??

在上面的結果中只取第一個

<*>會匹配'<H1>title</H1>’整個字符串(貪婪匹配),使用*?可以只找出<H1>(非貪婪匹配)。

{m}

對於前一個字符重復m次

a{3}匹配3個’a’,’aaa’可以匹配,但’aaba’無法匹配。

{m,n}

對於前一個字符重復m到n次

a{2,4}匹配2-4個a;a{2,}匹配2個以上a;a{,4}匹配4個以下a。

{m,n}?

對於前一個字符重復m到n次,並且取盡可能少的情況

在字符串’aaaaaa’中,a{2,4}會匹配4個a,但a{2,4}?只匹配2個a。

\

對特殊字符進行轉義,或者是指定特殊序列

 

[]

表示一個字符集

[abc]會匹配字符a,b或者c,[a-z]匹配所有小寫字母,[a-zA-Z0-9]匹配所有字母和數字,[^6]表示除了6以外的任意字符。注意[]一次只能匹配單個字符,一般配合[]*或[]{m,n}使用。

|

表示或者,只匹配其中一個表達式

A|B,如果A匹配了,則不再查找B,反之亦然。一般可以在[]或()中使用。

(pattern)

匹配括號中的任意正則表達式,並捕獲其結果放到一個分組中

([\d]*)會匹配任意數字,並把匹配到的結果放到當前分組中,默認此分組number為1。

(pattern)\<number>

使用\<number>可以引用編號為number的分組匹配到的字符串

([\d]*)\1表達式就等於把([\d]*)匹配到的內容再次引用,比如說([\d]*)能匹配121,而([\d]*)\1能匹配121121。

(?P<name>pattern)

給分組起一個別名,在引用時比number更好引用

(?P<digit>[\d]*)表達式就是匹配任意數字,並給匹配到的數字一個digit別名。

(?P=name>)

引用別名為name的分組匹配到的字符串

其實跟\<number>有點類似,不過引用從number變成name了。比如(?P<digit>[\d]*)把匹配到的數字設置一個別名,而(?P<digit>[\d]*)(?P=digit)就是根據引用別名再次匹配。

(?#commentpattern)

允許在正則表達式中寫入注釋,在’(?#’ ‘)’之間的內容將被忽略。

正則表達式’(?<=abc)def’會在’abcdef’中匹配’def’。

     

包含’ \ ’的特殊序列的意義如下表:

特殊表達式序列

意義

\n

匹配一個換行符,等價於\x0a和\cJ。

\b

匹配一個單詞邊界,也就是指單詞和空格間的位置。例如,’er\b’可以匹配”never”中的’er’,但不能匹配”verb”中的’er’。

\B

匹配非單詞邊界,’er\B’能匹配”verb”中的’er’,但不能匹配”never”中的’er’。

\d

匹配任意十進制數,相當於[0-9]。

\D

匹配任意非數字字符,相當於[^0-9]。

\s

匹配任意空白字符,相當於[ \t\n\r\f\v]。

\S

匹配任意非空白字符,相當於[^ \t\n\r\f\v]。

\w

匹配任意數字和字母,相當於[a-zA-Z0-9_]。

\W

匹配任意非數字和字母的字符,相當於[^a-zA-Z0-9_]。

\r

匹配一個回車符,等價於\x0d和\cM。

 

三、Python re使用

Python的re正則表達式模塊定義了一系列函數,常量以及異常;同時,正則表達式被編譯成‘ RegexObject ’實例,本身可以為不同的操作提供方法。接下來簡要介紹一下這些函數的功能和用法。

1. compile

re.compile(pattern[, flags])

把正則表達式的模式和標識轉化成正則表達式對象,就是把規則編譯好,供match()和search()這兩個函數使用。

re所定義的flag包括:

re.I:忽略大小寫。

re.L:表示特殊字符集\w, \W, \b, \B, \s, \S依賴於當前環境。

re.M:多行模式。

re.S:即為’ . ’並且包括換行符在內的任意字符(’ . ’不包括換行符)。

re.U:表示特殊字符集\w, \W, \b, \B, \d, \D, \s, \S依賴於Unicode字符屬性數據庫。

re.X:為了增加可讀性,忽略空格和’ # ’后面的注釋。

例:以下兩種用法結果相同,只是第一種編譯過,重復使用效率更好;第二種是沒有編譯過的。

第一種:

>>> pattern = re.compile(r'^a') >>> pattern.match('abc') <_sre.SRE_Match object; span=(0, 1), match='a'>

當把正則表達式規則實例化之后,就會對應生成有很多實例屬性與方法,如:match()、findall()、finditer()、split()、sub()、subn()、fullmatch()、search()、scanner()、flags、groupindex、groups、pattern等。

# 實例屬性信息;
>>> pattern.flags 32 >>> pattern.pattern '^a'

第二種

>>> re.match(r'^a', 'abc') <_sre.SRE_Match object; span=(0, 1), match='a'>

上面兩種方式,任意一種如果匹配成功,則會返回一個對象,以及匹配范圍和匹配到的值。接下來就可以把此對象實例化:

>>> pattern = re.compile(r'a') >>> data = pattern.match('abc abc')

對應也會生成很多實例屬性和方法,如:end()、expand()、group()、groupdict()、groups()、span()、start()、endpos、pos、re、regs、string、lastgroup、lastindex等。

# 顯示被匹配字符串;
>>> data.string 'abc abc' # 顯示匹配規則實例; >>> data.re re.compile('^a') # 顯示匹配結果; >>> data.group() 'a' # 匹配結果在原字符串中的索引位置; >>> data.span() (0, 1) # 顯示從什么索引位置開始匹配; >>> data.start() 0 # 顯示匹配到什么索引位置結束; >>> data.end() 1

對於groups()方法,是把匹配結果以組的方式返回,是一個元祖;但有個條件就是正則表達式以組的形式匹配才行,如下:

>>> pattern = re.compile(r'a(\w*) a(\w*)') >>> data = pattern.match('abc abc') >>> data.group() 'abc abc' >>> data.groups() ('bc', 'bc')

對於groupdict()方法,是把匹配結果以dict方式顯示;但正則匹配條件必須以組形式匹配,並且賦值一個key才行,如下:

>>> data = re.match(r'(?P<mail>[a-zA-Z0-9]{6,11}@163.com)', '12100231231@163.com') >>> data.groupdict() {'mail': '12100231231@163.com'}

更多屬性與方法自己嘗試

# 可以看到,我們在寫規則時都在前面加了一個‘r’字符,是為了用來表明r”內的字符都無須轉義,不然當我們包含一個’\n’時可能就會被轉義為換行符,就無法做正確匹配操作了。

2. search

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

在字符串中查找匹配正則表達式模式的位置,返回MatchObject的實例,如果沒有找到匹配的位置,則返回None。

第一個參數:匹配規則

第二個參數:表示要匹配的字符串

第三個參數:標致位,用於控制正則表達式的匹配方式,比如上面介紹的大小寫,多行匹配等

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:search (string[, pos[, endpos]])

若regex是已編譯好的正則表達式對象,regex.search(string, 0, 50)等同於regex.search(string[:50], 0)。

具體示例如下:

>>> pattern = re.compile(r"a") # 匹配成功; >>> pattern.search("abcde") <_sre.SRE_Match object; span=(0, 1), match='a'> >>> pattern.search("bcade") <_sre.SRE_Match object; span=(2, 3), match='a'> # 未匹配成功; >>> pattern.search("abcde", 1) # \b界定符使用; >>> re.search(r'de\b', "abcde") <_sre.SRE_Match object; span=(3, 5), match='de'> >>> re.search(r'de\b', "abcdeef")

3. match

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

嘗試從字符串的起始位置嘗試匹配一個正則表達式,也等於說是從第一個字符開始匹配。

第一個參數:匹配規則

第二個參數:表示要匹配的字符串

第三個參數:標致位,用於控制正則表達式的匹配方式,比如上面介紹的大小寫,多行匹配等

>>> re.match('a','ab bc cd') #能匹配到; >>> re.match('b','ab bc cd') #不能匹配到;

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:match (string[, pos[, endpos]])

match()函數只在字符串的開始位置嘗試匹配正則表達式,也就是只報告從位置0開始的匹配情況,而search()函數是掃描整個字符串來查找匹配。如果想要搜索整個字符串來尋找匹配,應當用search()。

案例一:匹配特殊符號

 >>> re.match(r"\[[\w]\]", '[a]') <_sre.SRE_Match object; span=(0, 3), match='[a]'>

案例二:匹配IP地址

>>> ip = "172.16.10.1" >>> re.match(r'\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', ip) <_sre.SRE_Match object; span=(0, 11), match='172.16.10.1'>

案例三:匹配郵箱地址

>>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3}', '12100231231@163.com') <_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'> >>> re.match(r'[a-zA-Z0-9]{6,11}@[a-z0-9]*\.com$', '12100231231@163.com') <_sre.SRE_Match object; span=(0, 19), match='12100231231@163.com'>

案例三:分組匹配

>>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@163.com') >>> data.groups() ('163',) >>> data = re.match(r'[a-zA-Z0-9]{6,11}@(163|126).com', '12100231231@126.com') >>> data.groups() ('126',) >>> data = re.match(r'[a-zA-Z0-9]{11}@(.*),[\w]*@(.*)', '12100231231@163.com,abc@126.com') >>> data.group() '12100231231@163.com,abc@126.com' >>> data.groups() ('163.com', '126.com')

案例四:分組匹配並起別名

>>> data = re.match(r'(?P<name>\w*) "(?P<mail>[a-zA-Z0-9]{6,11}@[a-z0-9]*\.[a-z]{1,3})" (?P<age>\d{1,3})', 'dkey "12100231231@163.com" 23') >>> data.groupdict() {'mail': '12100231231@163.com', 'name': 'dkey', 'age': '232'}

 

4. split

re.split(pattern, string[, maxsplit=0, flags=0])

此功能很常用,可以將正則表達式匹配的部分進行分割,並返回一個列表。我們在python中,使用str的方法split也可以做字符串分割,但是使用正則會方便很多。

第一個參數:匹配規則

第二個參數:字符串

第三個參數:最大分割次數,默認為0,表示每個匹配項都分割

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:split(string[, maxsplit=0])

例如,利用上面章節中介紹的語法:

# 以:或空格分割; >>> program = "ywnds:C C++ Java Python" >>> re.split(r':| ', program) ['ywnds', 'C', 'C++', 'Java', 'Python'] # 以:或空格或,分割; >>> program = "ywnds:C C++ Java Python,Go" >>> re.split(r':| |,', program) ['ywnds', 'C', 'C++', 'Java', 'Python', 'Go']

對於一個找不到匹配的字符串而言,split不會對其作出分割,如:

>>> re.split(r'a', 'hello world') ['hello world']

5. findall

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

在字符串中查找到正則表達式所匹配的所有字符,並組成一個列表返回。跟search方法最大的區別就在於search只會查找到第一個匹配值后就返回,而findall是查找所有。

第一個參數:匹配規則

第二個參數:目標字符串

但三個參數:后面還可以跟一個規則選擇項

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:findall(string[, pos[, endpos]])

示例如下:

>>> re.findall(r"\d+", 'python=90, java=80, go=70') ['90', '80', '70'] >>> re.search(r"\d+", 'python=90, java=80, go=70') <_sre.SRE_Match object; span=(7, 9), match='90'>

6. finditer

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

和findall方法類似,在字符串中找到正則表達式所匹配的所有字符,並組成一個迭代器返回。

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:finditer(string[, pos[, endpos]])

7. sub

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

將字符串中匹配到正則表達式的部分用另一個字符串repl進行替換。如果沒有找到匹配pattern的字符,則返回未被修改的string。repl既可以是字符串也可以是一個函數。

第一個參數:匹配規則

第二個參數:替換后的字符串

第三個參數:字符串

第四個參數:替換個數,默認為0,表示每個匹配項都替換

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:sub(repl, string[, count=0])

此語法的示例有:

>>> p = re.compile(r'(one|two|three)') >>> p.sub( 'num', 'one word two words three words') 'num word num words num words'

同樣可以用以下方法,並指定count為1(只替換第一個):

>>> p.sub( 'num', ' one word two words three words', count=1) ' num word two words three words'

上面說了,repl也可以是一個函數,下面我們測試一下看看:

def add(v): var = v.group() num = int(var) + 1 return str(num)

上面定義了一個函數,默認會接收一個實例化對象,必須返回一個str對象。

>>> incr = "score = 90" >>> incr = re.sub(r'[\d]+', add, incr) >>> incr 'score = 91' >>> incr = re.sub(r'[\d]+', add, incr) >>> incr 'score = 92'

8. subn

subn(pattern, repl, string[, count, flags])

該函數的功能和sub()相同,但它還返回新的字符串以及替換的次數。

對於已編譯的正則表達式對象來說(re.RegexObject),有方法:subn(repl, string[, count=0])


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM