Python系列之正則表達式詳解


Python 正則表達式模塊 (re) 簡介

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

表 1. 正則表達式元字符和語法
符號 說明 實例
. 表示任意字符,如果說指定了 DOTALL 的標識,就表示包括新行在內的所有字符。 'abc'  >>>'a.c'   >>>結果為:'abc' 
^ 表示字符串開頭。 'abc'  >>>'^abc'  >>>結果為:'abc'
$ 表示字符串結尾。 'abc'  >>>'abc$'  >>>結果為:'abc'
*, +, ? '*'表示匹配前一個字符重復 0 次到無限次,'+'表示匹配前一個字符重復 1次到無限次,'?'表示匹配前一個字符重復 0 次到1次

'abcccd'  >>>'abc*' >>>結果為:'abccc'

'abcccd' >>>'abc+'  >>>結果為:'abccc'

'abcccd' >>>'abc?'  >>>結果為:'abc'

*?, +?, ??    前面的*,+,?等都是貪婪匹配,也就是盡可能多匹配,后面加?號使其變成惰性匹配即非貪婪匹配

'abc'  >>>'abc*?' >>>結果為:'ab'

'abc'  >>>'abc??' >>>結果為:'ab'

'abc'  >>>'abc+?' >>>結果為:'abc'

{m} 匹配前一個字符 m 次 'abcccd' >>>'abc{3}d'  >>>結果為:'abcccd'
{m,n} 匹配前一個字符 m 到 n 次 'abcccd'  >>> 'abc{2,3}d' >>>結果為:'abcccd'
{m,n}? 匹配前一個字符 m 到 n 次,並且取盡可能少的情況  'abccc'  >>> 'abc{2,3}?' >>>結果為:'abcc'
\ 對特殊字符進行轉義,或者是指定特殊序列  'a.c' >>>'a\.c' >>> 結果為: 'a.c'
[]                  表示一個字符集,所有特殊字符在其都失去特殊意義,只有: ^  -  ]  \   含有特殊含義 'abcd' >>>'a[bc]' >>>結果為:'ab'
| 或者,只匹配其中一個表達式 ,如果|沒有被包括在()中,則它的范圍是整個正則表達式 'abcd' >>>'abc|acd' >>>結果為:'abc'
( … ) 被括起來的表達式作為一個分組. findall 在有組的情況下只顯示組的內容  'a123d' >>>'a(123)d' >>>結果為:'123'
(?#...) 注釋,忽略括號內的內容  特殊構建不作為分組  'abc123' >>>'abc(?#fasd)123' >>>結果為:'abc123'
(?= … ) 表達式’…’之前的字符串,特殊構建不作為分組 在字符串’ pythonretest ’中 (?=test) 會匹配’ pythonre ’
(?!...) 后面不跟表達式’…’的字符串,特殊構建不作為分組 如果’ pythonre ’后面不是字符串’ test ’,那么 (?!test) 會匹配’ pythonre ’
(?<= … ) 跟在表達式’…’后面的字符串符合括號之后的正則表達式,特殊構建不作為分組 正則表達式’ (?<=abc)def ’會在’ abcdef ’中匹配’ def ’
(?:) 取消優先打印分組的內容 'abc' >>>'(?:a)(b)' >>>結果為'[b]'
?P<> 指定Key 'abc' >>>'(?P<n1>a)>>>結果為:groupdict{n1:a}
表 2. 正則表達式特殊序列
特殊表達式序列 說明
\A 只在字符串開頭進行匹配。
\b 匹配位於開頭或者結尾的空字符串
\B 匹配不位於開頭或者結尾的空字符串
\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_]
\Z 只在字符串結尾進行匹配

上面提到貪婪匹配和非貪婪匹配請看例子:

import re
#貪婪
ret_greed= re.findall(r'a(\d+)','a23b') 
print(ret_greed)
#非貪婪
ret_no_greed= re.findall(r'a(\d+?)','a23b')
print(ret_no_greed)

['23']
['2']

由於貪婪匹配為盡可能的多匹配所以結果為23 ,有人好奇了,findall是什么鬼 ,請耐心往下看:

re模塊

正則表達式使用反斜杠” \ “來代表特殊形式或用作轉義字符,這里跟Python的語法沖突,因此,Python用” \\ “表示正則表達式中的” \ “,因為正則表達式中如果要匹配” \ “,需要用\來轉義,變成” \ “,而Python語法中又需要對字符串中每一個\進行轉義,所以就變成了” \\ “。

上面的寫法是不是覺得很麻煩,為了使正則表達式具有更好的可讀性,Python特別設計了原始字符串(raw string),需要提醒你的是,在寫文件路徑的時候就不要使用raw string了,這里存在陷阱。raw string就是用’r’作為字符串的前綴,如 r”\n”:表示兩個字符”\”和”n”,而不是換行符了。Python中寫正則表達式時推薦使用這種形式。

1、 re.findall(pattern, string[, flags]):

方法能夠以列表的形式返回能匹配的子串。先看簡單的例子:

import re
a = 'one1two2three3four4'
ret = re.findall(r'(\d+)',a)
print(ret)
['1', '2', '3', '4']

從上面的例子可以看出返回的值是個列表,並且返回字符串中所有匹配的字符串。 

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

搜索string,返回一個順序訪問每一個匹配結果(Match對象)的迭代器。 請看例子:

import re
 
p = re.compile(r'\d+')
for m in p.finditer('one1two2three3four4'):
    print m.group(),

### output ###
# 1 2 3 4 

3、re.match和re.search

Python提供了兩種不同的原始操作:match和search。match是從字符串的起點開始做匹配,而search(perl默認)是從字符串做任意匹配。看個例子:

import re

ret_match= re.match("c","abcde");     #從字符串開頭匹配,匹配到返回match的對象,匹配不到返回None
if(ret_match):
    print("ret_match:"+ret_match.group());
else:
    print("ret_match:None");

ret_search = re.search("c","abcde"); #掃描整個字符串返回第一個匹配到的元素並結束,匹配不到返回None
if(ret_search):
    print("ret_search:"+ret_search.group());

ret_match:None
ret_search:c

   re.match對象擁有以下方法:

import re
a = "123abc456"
ret_match= re.match("a","abcde");
print(ret_match.group())  #返回返回被 RE 匹配的字符串
print(ret_match.start())  #返回匹配開始的位置
print(ret_match.end())    #返回匹配結束的位置
print(ret_match.span())   #返回一個元組包含匹配 (開始,結束) 的位置

其中group()方法可以指定組號,如果組號不存在則返回indexError異常看如下例子:

import re
a = "123abc456"
re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0)   #123abc456,返回整體默認返回group(0)
re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1)   #123
re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2)   #abc
re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3)   #456

4、re.sub和re.subn

 兩種方法都是用來替換匹配成功的字串,值得一提的時,sub不僅僅可以是字符串,也可以是函數。subn函數返回元組,看下面例子:

import  re
#sub
ret_sub = re.sub(r'(one|two|three)','ok','one word two words three words') #ok word ok words ok words
#subn
import  re
ret_subn = re.subn(r'(one|two|three)','ok','one word two words three words') #('ok word ok words ok words', 3) 3,表示替換的次數

  

5、re.split(pattern, string, maxsplit=0)

通過正則表達式將字符串分離。如果用括號將正則表達式括起來,那么匹配的字符串也會被列入到list中返回。maxsplit是分離的次數,maxsplit=1分離一次,默認為0,不限制次數。看一下例子:

import re
ret = re.split('\d+','one1two2three3four4') #匹配到1的時候結果為'one'和'two2three3four4',匹配到2的時候結果為'one', 'two'和'three3four4', 所以結果為:
####output####
['one', 'two', 'three', 'four', '']

  

6、re.compile(strPattern[, flag])

這個方法是Pattern類的工廠方法,用於將字符串形式的正則表達式編譯為Pattern對象。第二個參數flag是匹配模式,取值可以使用按位或運算符’|’表示同時生效,比如re.I | re.M。另外,你也可以在regex字符串中指定模式,比如re.compile(‘pattern’, re.I | re.M)與re.compile(‘(?im)pattern’)是等價的。可選值有:

re.I(IGNORECASE): 忽略大小寫(括號內是完整寫法,下同)
re.M(MULTILINE): 多行模式,改變'^'和'$'的行為(參見上圖)
re.S(DOTALL): 點任意匹配模式,改變'.'的行為
re.L(LOCALE): 使預定字符類 \w \W \b \B \s \S 取決於當前區域設定
re.U(UNICODE): 使預定字符類 \w \W \b \B \s \S \d \D 取決於unicode定義的字符屬性
re.X(VERBOSE): 詳細模式。這個模式下正則表達式可以是多行,忽略空白字符,並可以加入注釋。

請看例子:

import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
regex = re.compile(r'\w*oo\w*')
print(regex.findall(text))

['JGood', 'cool']

re 小節

#re.match()
#re.search()
#re.findall()
#re.split()
#re.sub()

re.match()、re.search()可以分為兩種情況,第一種不分組第二種分組(方便記憶):

a = 'hello word diao zha tian'
ret = re.match('h\w+',a)
print(ret.group()) #獲取匹配到的所有結果
print(ret.groups()) # 獲取模型中匹配到的分組情況
print(ret.groupdict())# 獲取模型中匹配到的分組結果
############output##############
hello
()
{}

ret = re.match('(?P<n>h)(?P<n1>\w+)',a)
print(ret)
print(ret.group()) #獲取匹配到的所有結果
print(ret.groups()) # 獲取模型中匹配到的分組情況
print(ret.groupdict())# 獲取模型中匹配到的分組結果
#?P<n1> = Key
############output##############

<_sre.SRE_Match object; span=(0, 5), match='hello'>
hello
('h', 'ello')
{'n': 'h', 'n1': 'ello'}
View Code

re.findall()

a = 'hello alex alex adn acd'
n = re.findall('(a)(\w+)',a)  #[('a', 'lex'), ('a', 'lex'), ('a', 'dn'), ('a', 'cd')]
print(n)                            #從左到右,從外到內 ,?P<> 無效
View Code

re.split()

在編寫計算器的時候可以用re.split() 比如:

def f1(ex):
    return eval(ex)  #測試用 真實中要自己編寫四則運算
 
a = '1*2+(5/6)+(12*23)/15'
while True:
    ret = re.split('\(([^()]+)\)', a, 1)
    if len(ret) == 3:
        a,b,c = re.split('\(([^()]+)\)', a, 1)
        rec = f1(b)
        a = a + str(rec) + c
    else:
        red = f1(a)
        print(red)
        break
re匹配括號

re.sub()  用於替換匹配的字符串

content = "123abc456"
new_content = re.sub('\d+', 'sb', content)
# new_content = re.sub('\d+', 'sb', content, 1)
print new_content 
View Code

 常用的驗證規則:

驗證手機號:
(^(13\d|14[57]|15[^4\D]|17[13678]|18\d)\d{8}|170[^346\D]\d{7})

驗證郵箱:
^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$


IP:
^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$
View Code

 

計算器源碼:

import re
def md(date_list,symbol):
    '''

    :param date_list: 匹配到的表達式
    :param symbol: 符號
    :return: 乘數計算得到的值
    '''
    a = date_list.index(symbol)  #取到符號
    if symbol == '*' and date_list[a + 1] != '-': #如果是乘號並且索引的下一個位置不是負號計算
        k = float(date_list[a - 1]) * float(date_list[a + 1])
    elif symbol == '/' and date_list[a + 1] != '-':  #如果是除號並且索引的下一個位置不是負號計算
        k = float(date_list[a - 1]) / float(date_list[a + 1])
    elif symbol == '*' and date_list[a + 1] == '-': #如果是乘號並且索引的下一個位置是負號計算
        k = -(float(date_list[a - 1]) * float(date_list[a + 2]))
    elif symbol == '/' and date_list[a + 1] == '-': #如果是除號並且索引的下一個位置是負號計算
        k = -(float(date_list[a - 1]) / float(date_list[a + 2]))
    del date_list[a - 1], date_list[a - 1], date_list[a - 1] #刪除列表里參與計算的索引位置
    date_list.insert(a - 1, str(k))  #把新的值插入到列表中
    return date_list
#處理混亂的四則,按照先算加減后乘除的原則
def fun(s):
    '''

    :param s: 去除括號后的表達式
    :return: 表達式的返回值
    '''
    list_str = re.findall('([\d\.]+|/|-|\+|\*)',s) #匹配表達式
    sum=0
    while 1:
        if '*' in list_str and '/' not in list_str:  #判斷乘是否在表達式內
            md(list_str, '*')
        elif '*' not in list_str and '/' in list_str: #判斷乘是否在表達式內
            md(list_str, '/')                   #調用md函數處理除號
        elif '*' in list_str and '/' in list_str:
            a = list_str.index('*')
            b = list_str.index('/')
            if a < b:
                md(list_str, '*')
            else:
                md(list_str, '/')
        else:
            if list_str[0]=='-':   #判斷是否是負號
                list_str[0]=list_str[0]+list_str[1]
                del list_str[1]
            sum += float(list_str[0])
            for i in range(1, len(list_str), 2):
                if list_str[i] == '+' and list_str[i + 1] != '-':
                    sum += float(list_str[i + 1])
                elif list_str[i] == '+' and list_str[i + 1] == '-':
                    sum -= float(list_str[i + 2])
                elif list_str[i] == '-' and list_str[i + 1] == '-':
                    sum += float(list_str[i + 2])
                elif list_str[i] == '-' and list_str[i + 1] != '-':
                    sum -= float(list_str[i + 1])
            break
    return sum



a='1 - 2 * ( (60-30 +(-40.0/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )'
#循環去除括號
while True:
    ret = re.split('\(([^()]+)\)', a, 1)
    if len(ret) == 3:
        a,b,c = re.split('\(([^()]+)\)', a, 1)
        rec = fun(b)
        a = a + str(rec) + c

    else:
        red = fun(a)
        print(red)
        break
View Code

 


免責聲明!

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



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