【Python基础】Python正则表达式入门到入魔
關于正則表達式,很多人認為,使用的時候查詢下就可以,沒必要深入學習,但是知識與應用永遠都是螺旋辯證的關系,有需要查詢也無可厚非,但是先掌握知識,可以讓應用更創新,更深入,超越他人,必須要先掌握大量的深刻的知識。
如果正則學的好,你會發現它功能強大、妙用無窮。對于很多實際工作來講,正則表達式簡直是靈丹妙藥,能夠成百倍地提高開發效率和程序質量。
但是學習正則并非易事:知識點瑣碎、記憶點多、符號亂,難記憶、難描述、廣而深且,為此,我做了一個全面的整理,希望能幫助到大家。
正則表達式是一個復雜的主題,因此本文非常長,作者能力有限,錯誤之處,懇請指正,為了方便閱讀,先看文章目錄:
一、起源發展
? 1、20世紀40年代
??2、20世紀50年代
??3、20世紀60年代
??4、20世紀80年代
二、學習資料
??1、re模塊官方文檔(中文版)
??2、Python內置模塊
??3、re模塊庫源代碼
? 4、正則表達HOWTO
??5、學習網站
三、re模塊簡介
四、語法原理
??1、基本元字符
??2、數量元字符
??3、位置元字符
??4、特殊元字符
??5、前后查找
??6、回溯引用
??7、大小寫轉換
五、常量模塊
??1、IGNORECASE
??2、ASCII
??3、DOTALL
??4、MULTILINE
??5、VERBOSE
??6、LOCALE
??7、UNICODE
??8、DEBUG
??9、TEMPLATE
??10、常量總結
六、函數模塊
??1、查找一個匹配項
?????1)search()
??? ?2)match()
??? ?3)fullmatch()
? 2、查找多個匹配項
???? 1)findall()
??? ?2)finditer()
??3、匹配項分割
??????1)split()
??4、匹配項替換
??? ?1)sub()
??? ?2)subn()
??5、編譯正則對象
??6、轉義函數
??7、緩存清除函數
六、異常模塊
一、起源發展
我們在學習一門技術的時候有必要簡單的了解其起源與發展過程,這對學習知識有一定的幫助。
1、20世紀40年代
正則表達式最初的想法來自兩位神經學家:沃爾特·皮茨與麥卡洛克,他們研究出了一種用數學方式來描述神經網絡的模型。
2、20世紀50年代
一位名叫Stephen Kleene的數學科學家發表了一篇題目是《神經網事件的表示法》的論文,利用稱之為正則集合的數學符號來描述此模型,引入了正則表達式的概念。正則表達式被作為用來描述其稱之為“正則集的代數”的一種表達式,因而采用了“正則表達式”這個術語。
3、20世紀60年代
C語言之父、UNIX之父肯·湯普森把這個“正則表達式”的理論成果用于做一些搜索算法的研究,他描述了一種正則表達式的編譯器,于是出現了應該算是最早的正則表達式的編譯器qed(這也就成為后來的grep編輯器)。
Unix使用正則之后,正則表達式不斷的發展壯大,然后大規模應用于各種領域,根據這些領域各自的條件需要,又發展出了許多版本的正則表達式,出現了許多的分支。我們把這些分支叫做“流派”。
4、20世紀80年代
Perl語言誕生了,它綜合了其他的語言,用正則表達式作為基礎,開創了一個新的流派,Perl流派。之后很多編程語言如:Python、Java、Ruby、.Net、PHP等等在設計正則式支持的時候都參考Perl正則表達式。到這里我們也就知道為什么眾多編程語言的正則表達式基本一樣,因為他們都師從Perl。注:Perl語言是一種擅長處理文本的語言,Larry在1987年創建,Practical Extraction and Report Language,實用報表提取語言,但因晦澀語法和古怪符號不利于理解和記憶導致很多開發者并不喜歡。
二、學習資料
Python對正則表達式的支持,主要是re庫,這是一個Python的標準庫。也就是該庫是一個內置模塊(Python大概300個內置模塊),不需要額外的下載,使用的時候,直接 import re 加載即可。下面是一些常用的學習資料鏈接。
1、re模塊官方文檔(中文版)
https://docs.python.org/zh-cn/3.7/library/re.html
2、Python內置模塊
https://docs.python.org/3/py-modindex.html#cap-r
3、re模塊庫源代碼
https://github.com/python/cpython/blob/3.8/Lib/re.py
4、正則表達HOWTO
https://docs.python.org/zh-cn/3.7/howto/regex.html#regex-howto ? ? ? ? ? ? ? ? ? ? ?
5、學習網站
https://www.runoob.com/python/python-reg-expressions.html
?
三、re模塊簡介
Python的re模塊主要定義了9個常量、12個函數、1個異常,下面先講一些基本原理后,慢慢介紹具體的函數用法。
import?re print(dir(re)) ['A',?'ASCII',?'DEBUG',?'DOTALL',?'I',?'IGNORECASE',?'L',?'LOCALE',?'M',?'MULTILINE',?'Match',? 'Pattern',?'RegexFlag',?'S',?'Scanner',?'T',?'TEMPLATE',?'U',?'UNICODE',?'VERBOSE',?'X',?...,? 'compile',?'copyreg',?'enum',?'error',?'escape',?'findall',?'finditer',?'fullmatch',?'functools',? 'match',?'purge',?'search',?'split',?'sre_compile',?'sre_parse',?'sub',?'subn',?'template'] ?四、語法原理
完整的正則表達式由兩種字符構成:特殊字符(元字符)和普通字符。元字符表示正則表達式功能的最小單位,如*?^?$?\d等
1、基本元字符
.
在默認模式,匹配除了換行的任意字符。如果指定了標簽re.DOTALL,它將匹配包括換行符的任意字符。
import?re #除了換行符\n,其他都匹配出來了 re.findall(r'.','小伍哥\n真帥') ['小',?'伍',?'哥',?'真',?'帥']#加了條件,都匹配出來了 re.findall(r'.','小伍哥\n真帥',re.DOTALL) ['小',?'伍',?'哥',?'\n',?'真',?'帥']#匹配'd...c',三個點表示中間三個字符 re.findall(r'd...c','abd9匹配cdd') ['d9匹配c']\
轉義特殊字符(允許你匹配'*','?', 或者此類其他特殊字符),或者表示一個特殊序列;如果你沒有使用原始字符串(r'raw')來表達樣式,要牢記Python也使用反斜杠\作為轉義序列;如果轉義序列不被Python的分析器識別,反斜杠和字符才能出現在字符串中。如果Python可以識別這個序列,那么反斜杠就應該重復兩次。這將導致理解障礙,所以高度推薦,就算是最簡單的表達式,也要使用原始字符串。
[]
用于表示一個字符集合。在一個集合中:
1)字符可以單獨列出,比如[amk]匹配'a','m',或者'k'。
re.findall(r'[小帥]','小伍哥真帥') ['小',?'帥']2)可以表示字符范圍,通過用'-'將兩個字符連起來。比如[a-z]將匹配任何小寫ASCII字符,[0-5][0-9]將匹配從00到59的兩位數字,[0-9A-Fa-f]將匹配任何十六進制數位。如果-進行了轉義(比如[a\-z])或者它的位置在首位或者末尾(如[-a]或[a-]),它就只表示普通字符'-'。
#匹配從00到59的兩位數字 re.findall(r'[0-5][0-9]','012234456789') ['01',?'22',?'34',?'45']3)特殊字符在集合中,失去它的特殊含義。如[(+*)]只會匹配這幾個文法字符'(', '+', '*', 或')'。
4)字符類如\w或者\S(如下定義) 在集合內可以接受,它們可以匹配的字符由ASCII或LOCALE模式決定。
5)不在集合范圍內的字符可以通過取反來進行匹配。如果集合首字符是'^',所有不在集合內的字符將會被匹配,比如[^5]?將匹配所有字符,除了'5',[^^]將匹配所有字符,除了'^'.^如果不在集合首位,就沒有特殊含義。
#除了TM,都匹配出來 re.findall(r'[^TM]','小伍哥真TM帥') ['小', '伍', '哥', '真', '帥']|
A|B,A和B可以是任意正則表達式,創建一個正則表達式,匹配A或者B.,任意個正則表達式可以用'|'連接。它也可以在組合(見下列)內使用。掃描目標字符串時,'|'分隔開的正則樣式從左到右進行匹配。當一個樣式完全匹配時,這個分支就被接受。意思就是,一旦A匹配成功,B就不再進行匹配,即便它能產生一個更好的匹配。或者說'|'操作符絕不貪婪。如果要匹配'|'字符,使用\|,或者把它包含在字符集里,比如[|]。
-
可以表示字符范圍,通過用'-'將兩個字符連起來。比如[a-z]將匹配任何小寫ASCII字符,[0-5][0-9]將匹配從00到59的兩位數字,[0-9A-Fa-f]將匹配任何十六進制數位。
re.findall(r'[1-3]','012234456789')#查找所有1-3之間的自然數 ['1', '2', '2', '3']()?
匹配器整體為一個原子,即模式單元可以理解為多個原子組成的大原子
?
2、數量元字符
*
對它前面的正則式匹配0到任意次重復,ab*會匹配?'a','ab','abbb...',注意只重復b,盡量多的匹配字符串。
re.findall(r'ab*','ababbbbaabbcaacb') ['ab',?'abbbb',?'a',?'abb',?'a',?'a'] #重復.0到任意次,.有代表任意字符,所有d.*c能匹配dc之間包含任意字符 re.findall(r'd.*c','abd9我愛學習cdd') ['d9我愛學習c']+
對它前面的正則式匹配1到任意次重復。ab+會匹配'a'后面跟隨1個以上到任意個'b',它不會匹配'a'。
re.findall(r'ab+','ababbbbaabbcaacb') ['ab',?'abbbb',?'abb']?
對它前面的正則式匹配0到1次重復。ab?會匹配'a'或者'ab'。
re.findall(r'ab?','ababbbbaabbcaacb') ['ab', 'ab', 'a', 'ab', 'a', 'a']*?,?+?,???
'*','+',和'?'的懶惰模式,'*','+','?'修飾符都是貪婪的,它們在字符串進行盡可能多的匹配。有時候并不需要這種行為。在修飾符之后添加'?'將使樣式以非貪婪方式或者dfn最小方式進行匹配,盡量少的字符將會被匹配。
#看結果,確實有點太懶惰了,只匹配了a re.findall(r'ab*?','ababbbbaabbcaacb')['a', 'a', 'a', 'a', 'a', 'a']{m}
對其之前的正則式指定匹配m個重復,少于m的話就會導致匹配失敗。比如,a{6}將匹配6個'a', 但是不能是5個。
#a后面2個b re.findall(r'ab{2}','ababbbbaabbcaacb') ['abb',?'abb']{m,n}
對正則式進行m到n次匹配,在m和n之間取盡量多,比如a{3,5}將匹配3到5個'a'。忽略m意為指定下界為0,忽略n指定上界為無限次。比如a{4,}b將匹配'aaaab'或者10000000個'a'尾隨一個'b',但不能匹配'aaab'。逗號不能省略,否則無法辨別修飾符應該忽略哪個邊界。
re.findall(r'ab{2,4}','ababbbbaabbcaacb') ['abbbb',?'abb']re.findall(r'a{2}b{2,4}','ababbbbaabbcaacb') ['aabb'] {m,} 匹配前一個字符(或者子表達式至少m次) re.findall(r'ab{2,}','ababbbbaabbcaacb') ['abbbb',?'abb']{m,n}?
上一個修飾符的非貪婪模式,只匹配盡量少的字符次數。比如對于'aaaaaa',a{3,5}匹配5個'a',而a{3,5}?只匹配3個'a'。
re.findall(r'ab{2,4}?','ababbbbaabbcaacb') ['abb', 'abb']re.findall(r'a{3,5}','aaaaaa') ['aaaaa']re.findall(r'a{3,5}?','aaaaaa') ['aaa', 'aaa'3、位置元字符
^
匹配字符串的開頭是否包含正則表達式, 并且在MULTILINE模式也匹配換行后的首個符號。
#匹配字符串開頭是否包含emrT re.findall(r'^[emrT]','He?was?carefully') [] #匹配開頭是否包含emrH re.findall(r'^[emrH]','He?was?carefully')['H'] #匹配開頭是否包含2個以上的a re.findall(r'^a{2,}','aaabc'$
匹配字符串尾或者在字符串尾的換行符的前一個字符,在MULTILINE模式下也會匹配換行符之前的文本。foo匹配 'foo' 和 'foobar',但正則表達式foo$只匹配 'foo'。在'foo1\nfoo2\n'中搜索 foo.$,通常匹配 'foo2',但在MULTILINE模式下可以匹配到 'foo1';在?'foo\n'中搜索$會找到兩個(空的)匹配:一個在換行符之前,一個在字符串的末尾。
re.findall(r'foo','foo') ['foo']re.findall(r'foo','foobar') ['foo']re.findall(r'foo$','foo') ['foo']re.findall(r'foo$','foobar')[]re.findall(r'[ey]','He?was?carefully') ['e', 'e', 'y']re.findall(r'[ey]$','He?was?carefully') ['y']\A
只匹配字符串開始。
\Z
只匹配字符串尾。
\b
匹配空字符串,但只在單詞開始或結尾的位置。一個單詞被定義為一個單詞字符的序列。注意,通常\b定義為\w和\W字符之間,或者\w和字符串開始/結尾的邊界,意思就是r'\bfoo\b'匹配'foo','foo.','(foo)','bar foo baz'但不匹配'foobar'或者'foo3'。
默認情況下,Unicode字母和數字是在Unicode樣式中使用的,但是可以用ASCII標記來更改。如果 LOCALE 標記被設置的話,詞的邊界是由當前語言區域設置決定的,\b 表示退格字符,以便與Python字符串文本兼容。
\B
匹配空字符串,但不能在詞的開頭或者結尾。意思就是r'py\B'匹配'python','py3','py2', 但不匹配'py','py.', 或者'py!'.\B是\b的取非,所以Unicode樣式的詞語是由Unicode字母,數字或下劃線構成的,雖然可以用ASCII標志來改變。如果使用了LOCALE標志,則詞的邊界由當前語言區域設置。
4、特殊元字符
\d
對于Unicode (str) 樣式:匹配任何Unicode十進制數(就是在Unicode字符目錄[Nd]里的字符)。這包括了[0-9],和很多其他的數字字符。如果設置了ASCII標志,就只匹配[0-9]?。
對于8位(bytes)樣式:匹配任何十進制數,就是[0-9]。
re.findall(r'\d','chi13putao14butu520putaopi666') ['1',?'3',?'1',?'4',?'5',?'2',?'0',?'6',?'6',?'6']re.findall(r'\d+','chi13putao14butu520putaopi666') ['13',?'14',?'520',?'666']\D
匹配任何非十進制數字的字符。就是\d取非。如果設置了ASCII標志,就相當于[^0-9]?。
re.findall(r'\D','chi13putao14butu520putaopi666') ['c',?'h',?'i',?'p',?'u',?'t',?'a',?'o',?'b',?'u',?'t',?'u',? 'p',?'u',?'t',?'a',?'o',?'p',?'i']re.findall(r'\D+','chi13putao14butu520putaopi666') ['chi',?'putao',?'butu',?'putaopi']\s
對于 Unicode (str) 樣式:匹配任何Unicode空白字符(包括[?\t\n\r\f\v]?,還有很多其他字符,比如不同語言排版規則約定的不換行空格)。如果ASCII被設置,就只匹配?[?\t\n\r\f\v]?。
對于8位(bytes)樣式:匹配ASCII中的空白字符,就是[?\t\n\r\f\v]?。
\S
匹配任何非空白字符。就是\s取非。如果設置了ASCII標志,就相當于[^?\t\n\r\f\v]?。
re.findall(r'\S+','chi??putao14butu?putaopi666') ['chi',?'putao14butu',?'putaopi666']\w
對于Unicode (str) 樣式:匹配Unicode詞語的字符,包含了可以構成詞語的絕大部分字符,也包括數字和下劃線。如果設置了ASCII標志,就只匹配[a-zA-Z0-9_]?。
對于8位(bytes)樣式:匹配ASCII字符中的數字和字母和下劃線,就是[a-zA-Z0-9_]。如果設置了LOCALE標記,就匹配當前語言區域的數字和字母和下劃線。
#未設置,全部匹配 re.findall(r'\w+','chi小伍哥putao14butu真putaopi帥666') ['chi小伍哥putao14butu真putaopi帥666']#設置了re.ASCII,只匹配數字和字母 re.findall(r'\w+','chi小伍哥putao14butu真putaopi帥666',?re.ASCII) ['chi',?'putao14butu',?'putaopi',?'666']\W
匹配任何不是單詞字符的字符。這與\w正相反。如果使用了ASCII旗標,這就等價于[^a-zA-Z0-9_]。如果使用了LOCALE旗標,則會匹配在當前區域設置中不是字母數字又不是下劃線的字符。
re.findall(r'\W+','chi小伍哥putao14butu真putaopi帥666',?re.ASCII) ['小伍哥',?'真',?'帥'][\b]
它只在字符集合內表示退格,比如[\b]
?
5、前后查找
import?re from?urllib?import?request #爬蟲爬取百度首頁內容 data=request.urlopen("http://www.baidu.com/").read().decode() #分析網頁,確定正則表達式 pat=r'<title>(.*?)</title>' result=re.search(pat,data) print(result) <re.Match?object;?span=(1358,?1382),?match='<title>百度一下,你就知道</title>'> result.group() # 百度一下,你就知道 '<title>百度一下,你就知道</title>'在爬取的HTML頁面中,要匹配出一對標簽之間的文本,'<head><TITLE>welcome to my page</title></head>',即<title>與</title>之間的文本,方法如下:
#正則表達式 re.findall(r'<[Tt][Ii][Tt][Ll][Ee]>.*?</[Tt][Ii][Tt][Ll][Ee]>','<head><TITLE>welcome?to?my?page</title></head>') #結果 ['<TITLE>welcome?to?my?page</title>']分析:<[Tt][Ii][Tt][Ll][Ee]>表示不區分大小寫,這個模式匹配到了title標簽以及它們之間的文本,但是并不完美,因為我們只想要title標簽之間的文本,而不包括標簽本身。為了解決類似的問題,我們就需要用到前后查找的方法。
1)向前查找
向前查找指定了一個必須匹配但不在結果中返回的模式。向前查找實際上就是一個子表達式,它以?=開頭,需要匹配的文本跟在=的后面。
#使用向前查找 re.findall(r'<[Tt][Ii][Tt][Ll][Ee]>.*?(?=</[Tt][Ii][Tt][Ll][Ee]>)','<head><TITLE>welcome to my page</title></head>') #返回結果 ['<TITLE>welcome to my page'] #可以看大,后面個</title>已經沒有了,但是前面個還在,那要用到我們的向后查找 #看一個提取http的例子 re.findall(r'.+(?=:)','http://blog.csdn.net/mhmyqn') ['http'] '''分析:URL地址中協議部分是在:之前的部分,模式.+匹配任意文本,子表達式( ?=:)匹配:,但是被匹配到的:并沒有出現在結果中。我們使用?=向正則表達式引擎表明 ,只要找到:就行了,但不包括在最終的返回結果里。這里如果不使用向前匹配(?=:) ,而是直接使用(:),那么匹配結果就會是http:了,它包括了:,并不是我們想要的。'''2)向后查找
向后查找操作符是?<=。并不是所有的語言都支持向后查找,JavaScript就不支持,java語言支持向后查找。
比如要查找文本當中的價格(以$開頭,后面跟數字),結果不包含貨幣符號:
文本:category1:$136.25,category2:$28,category3:$88.60
正則表達式:(?<=\$)\d+(\.\d+)?
結果:category1:$【136.25】,category2:$【28】,category3:$【88.60】
分析:(?<=\$)模式匹配$,\d+(\.\d+)?模式匹配整數或小數。從結果可以看出,結果不沒有包括貨幣符號,只匹配出了價格。如果不使用向后查找,情況會是什么樣呢?使用模式$\d+(\.\d+)?,這樣會把$包含在結果中。使用模式\d+(\.\d+)?,又會把categery1(23)中的數字也匹配出來,都不是我們想要的。
注意:向前查找模式的長度是可變的,它們可以包含.、*、+之類的元字符;而向后查找模式只能是固定長度,不能包含.、*、+之類的元字符。
3)前后查找
把向前查找和向后查找結合起來使用,即可解決前面HTML標簽之間的文本的問題:
#正則表達 re.findall(r'(?<=<[Tt][Ii][Tt][Ll][Ee]>).*?(?=</[Tt][Ii][Tt][Ll][Ee]>)','<head><TITLE>welcome to my page</title></head>') #結果 ['welcome to my page']分析:從結果可以看出,問題完美的解決了。(?<=<[Tt][Ii][Tt][Ll][Ee]>)是一個向后操作,它匹配<title>但不消費它,(?=</[Tt][Ii][Tt][Ll][Ee]>)是一個向前操作,它匹配</title>但不消費它。最終返回的匹配結果只包含了標簽之間的文本了。
4)前后查找取非
前面說到的向前查找和向后查找通常都是用來匹配文本,其目的是為了確定將被返回的匹配結果的文本的位置(通過指定匹配結果的前后必須是哪些文本)。這種用法叫正向前查找和正向后查找。還有一種負向前查找和負向后查找,是查找那些不與給定模式相匹配的文本。
比如一段文本中即有價格(以$開頭,后面跟數字)和數量,我們要找出價格和數量,先來看查找價格:
文本:I paid $30 for 10 apples, 15 oranges, and 10 pears. I saved $5 onthis order.
正則表達式:(?<=\$)\d+
結果:I paid 【$30】 for 10 apples, 15 oranges, and 10 pears. I saved 【$5】 on thisorder.
re.findall(r'(?<=\$)\d+','I paid $30 for 10 apples, 15 oranges, and 10 pears. I saved $5 onthis order.')
['30', '5']
查找數量:
文本:I paid $30 for 10 apples, 15 oranges, and 10 pears. I saved $5 onthis order.
正則表達式:\b(?<!\$)\d+\b
結果:I paid $30 for 【10】 apples, 【15】 oranges, and 【10】pears. I saved $5 on this order.
分析:(?<!\$)表示一個負向后查找,它使得結果只包含那些不以$開頭的數值。
5)小結
前后查找的操作符:
(?=) | 正向前查找 |
(?!) | 負向前查找 |
(?<=) | 正向后查找 |
(?<!) | 負向后查找 |
有了前后查找,就可以對最終的匹配結果包含哪些內容做出精確的控制。前后查找操作使我們可以利用子表達式來指定文本匹配操作發生的位置,并收到只匹配不消費的效果。
6、回溯引用
回溯引用是正則表達式內的一個“動態”的正則表達式,讓你根據實際的情況變化進行匹配。說白了,就是一個根據你的需要,動態變化的表達式。
舉個栗子:
你原本要匹配<h1></h1>之間的內容,現在你知道HTML有多級標題,你想把每一級的標題內容都提取出來。你也許會這樣寫:
p?=?r"<h[1-6]>.*?</h[1-6]>"這樣一來,你就可以將HTML頁面內所有的標題內容全部匹配出來。即<h1></h1>到<h6></h6>的內容都可以被提取出來。但是我們之前說過,寫正則表達式困難的不是匹配到想要的內容,而是盡可能的不匹配到不想要的內容。在這個例子中,很有可能你就會被下面這樣的用例玩壞。
比方說
<h1>hello?world</h3>發現后面的</h3>了嗎?我們不管是怎么寫出來這樣的標題的,但實實在在的是我們的正則表達式同樣會把這里面的hello world匹配出來。這時候就是回溯引用的重要作用。下面就是一個示例:
import?re key?=?r"<h1>hello?world</h3>" p1?=?r"<h([1-6])>.*?</h\1>" pattern1?=?re.compile(p1) m1?=?re.search(pattern1,key) print m1.group(0)#這里是會報錯的,因為匹配不到,你如果將源字符串改成</h1> #結尾就能看出效果看到\1了嗎?原本那個位置應該是[1-6],但是我們寫的是\1,我們之前說過,轉義符\干的活就是把特殊的字符轉成一般的字符,把一般的字符轉成特殊字符。普普通通的數字1被轉移成什么了呢?在這里1表示第一個子表達式,也就是說,它是動態的,是隨著前面第一個子表達式的匹配到的東西而變化的。比方說前面的子表達式內是[1-6],在實際字符串中找到了1,那么后面的\1就是1,如果前面的子表達式在實際字符串中找到了2,那么后面的\1就是2。
類似的,\2,\3,....就代表第二個第三個子表達式。
所以回溯引用是正則表達式內的一個“動態”的正則表達式,讓你根據實際的情況變化進行匹配。
7、大小寫轉換
\E ? ?end,表示大小寫轉換的結束范圍
\l ? ? ?low,表示把下一個字符轉為小寫
\L Low,表示把\L與\E之間的字符轉為小寫
\u up,表示把下一個字符轉為大寫
\U Up,表示把\U與\E之間的字符轉為大寫
五、常量模塊
下面我們來快速學習這些常量的作用及如何使用他們,按常用度排序!
1、IGNORECASE
語法: re.IGNORECASE 或簡寫為 re.I
含義: 進行忽略大小寫匹配。
re.findall(r'TM','小伍哥tm真帥') [] re.findall(r'TM','小伍哥tm真帥',re.IGNORECASE) ['tm']在默認匹配模式下小寫字母tm無法匹配大寫字母TM的,而在忽略大小寫模式下就可以匹配了。
2、ASCII
語法: re.ASCII 或簡寫為 re.A
作用: 顧名思義,ASCII表示ASCII碼的意思,讓 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII,而不是Unicode。
re.findall(r'\w+','小伍哥tm真帥') ['小伍哥tm真帥']#w匹配所有字符re.findall(r'\w+','小伍哥tm真帥',re.ASCII)#w匹配字母和數字 ['tm']在默認匹配模式下'\w+'匹配到了所有字符串,而在ASCII模式下,只匹配到了a、b、c(也就是指匹配ASCII編碼支持的字符)。注意:這只對字符串匹配模式有效,對字節匹配模式無效。
3、DOTALL
語法: re.DOTALL 或簡寫為 re.S
作用: DOT表示.,ALL表示所有,連起來就是.匹配所有,包括換行符\n。默認模式下.是不能匹配行符\n的。
print('小伍哥\n真帥')#文本中間包含換行符,打印的時候會換行 小伍哥 真帥 re.findall(r'.*','小伍哥\n真帥') ['小伍哥', '', '真帥', ''] re.findall(r'.*','小伍哥\n真帥',re.DOTALL?) ['小伍哥\n真帥', '']在默認匹配模式下.并沒有匹配換行符\n,而是將字符串分開匹配;而在re.DOTALL模式下,換行符\n與字符串一起被匹配到。
4、MULTILINE
語法: re.MULTILINE 或簡寫為 re.M
含義: 多行模式,當某字符串中有換行符\n,默認模式下是不支持換行符特性的,比如:行開頭和行結尾,而多行模式下是支持匹配行開頭的。
print('小伍哥\n真帥')#文本中間包含換行符,打印的時候會換行 小伍哥 真帥 re.findall(r'^真帥','小伍哥\n真帥') []re.findall(r'^真帥','小伍哥\n真帥',re.MULTILINE) ['真帥']正則表達式中^表示匹配行的開頭,默認模式下它只能匹配字符串的開頭;而在多行模式下,它還可以匹配 換行符\n后面的字符。
注意:正則語法中^匹配行開頭、\A匹配字符串開頭,單行模式下它兩效果一致,多行模式下\A不能識別\n。
5、VERBOSE
語法: re.VERBOSE 或簡寫為 re.X
作用: 詳細模式,可以在正則表達式中加注解!
text??=?'小伍哥tm帥'pat???=?'''小伍哥?#?人名,本文作者tm # 強調程度帥 # 形容詞''' re.findall(pat,text) [] re.findall(pat,text,re.VERBOSE) ['小伍哥tm帥']可以看到,默認模式下無法識別正則表達式中的注釋,而詳細模式是可以識別的。當一個正則表達式十分復雜的時候,詳細模式或許能為你提供另一種注釋方式,但它不應該成為炫技的手段,建議謹慎使用!
6、LOCALE
語法: re.LOCALE 或簡寫為 re.L
作用: 由當前語言區域決定 \w, \W, \b, \B 和大小寫敏感匹配,這個標記只能對byte樣式有效,該標記官方已經不推薦使用,因為語言區域機制很不可靠,它一次只能處理一個 "習慣”,而且只對8位字節有效。
7、UNICODE
語法: re.UNICODE 或簡寫為 re.U
作用: 與 ASCII常量類似,匹配unicode編碼支持的字符,但是Python3默認字符串已經是Unicode,所以顯得有點多余。
8、DEBUG
語法: re.DEBUG
作用: 顯示編譯時的debug信息。
re.findall(r'tm','小伍哥tm真帥',re.DEBUG)LITERAL 116 LITERAL 1090. INFO 10 0b11 2 2 (to 11)prefix_skip 2prefix [0x74, 0x6d] ('tm')overlap [0, 0] 11: LITERAL 0x74 ('t') 13. LITERAL 0x6d ('m') 15. SUCCESS Out[97]: ['tm']?
9、TEMPLATE
語法: re.TEMPLATE 或簡寫為 re.T
作用: disable backtracking(禁用回溯),也就是在正則匹配的過程中,匹配錯誤后不進行回溯處理。
re.findall(r'ab{1,3}c','abbc') ['abbc'] re.findall(r'ab{1,3}c','abbc',re.TEMPLATE) error:?internal:?unsupported?template?operator?MAX_REPEAT我們看看正則一步一步分解匹配過程:
正則引擎先匹配 a。
正則引擎盡可能多地(貪婪)匹配 b{1,3}中的 b。
正則引擎去匹配 b,發現沒 b 了,糟糕!趕緊回溯!所以第一個案例沒禁止,能匹配,第二個案例有禁止,系統報錯
返回 b{1,3}這一步,不能這么貪婪,少匹配個 b。
正則引擎去匹配 b。
正則引擎去匹配 c,完成匹配。
以上,就是一個簡單的回溯過程,不進行回溯有可能匹配不到滿足條件的匹配項
?
10、常量總結
1)9個常量中,前5個(IGNORECASE、ASCII、DOTALL、MULTILINE、VERBOSE)有用處,兩個(LOCALE、UNICODE)官方不建議使用、兩個(TEMPLATE、DEBUG)試驗性功能,不能依賴。
2)常量在re常用函數中都可以使用,查看源碼可得知。
3)常量可疊加使用,因為常量值都是2的冪次方,所以是可以疊加使用的,疊加時請使用 | 符號而不是+ 符號!
re.findall(r'TM','小伍哥\ntm真帥') [] re.findall(r'TM','小伍哥\ntm真帥',re.IGNORECASE|re.MULTILINE) ['tm']六、函數模塊
正則表達re模塊共有12個函數,我將分類進行講解,這樣方便記憶與理解,先來看看概覽:
search、match、fullmatch:查找一個匹配項
findall、finditer:查找多個匹配項
split: 分割
sub,subn:替換
compile函數、template函數: 將正則表達式的樣式編譯為一個 正則表達式對象
1、查找一個匹配項
查找并返回一個匹配項的函數有3個:search、match、fullmatch,他們的作用分別是:
search:查找任意位置的匹配項
match:必須從字符串開頭匹配
fullmatch:整個字符串與正則完全匹配
1) search()
描述:在給定字符串中尋找第一個匹配正則表達式的子字符串,如果找到會返回一個Match對象,這個對象中的元素可以group()得到(之后將會介紹group的概念),如果沒找到就會返回None。調用re.match,re.search方法或者對re.finditer結果進行遍歷,輸出的結果都是re.Match對象
語法:re.search(pattern, string, flags=0)
pattern 匹配的正則表達式
string 要匹配的字符串
flags 標志位,用于控制正則表達式的匹配方式
假設返回的Match對象為m,m.group()來取某組的信息,group(1)返回與第一個子模式匹配的單個字符串,group(2)等等以此類推,start()方法得到對應組的開始索引,end()得到對應組的結束索引,span()以元組形式給出對應組的開始和結束位置,括號中填入組號,不填入組號時默認為0。
匹配對象m方法有很多,幾個常用的方法如下:
m.start()?返回匹配到的字符串的起使字符在原始字符串的索引
m.end()?返回匹配到的字符串的結尾字符在原始字符串的索引
m.group()?返回指定組的匹配結果
m.groups()?返回所有組的匹配結果
m.span() 以元組形式給出對應組的開始和結束位置
其中的組,是指用()括號括起來的匹配到的對象,比如下列中的"(\w)(.\d)",就是兩個組,第一個組匹配字母,第二個組匹配.+一個數字。
m?=?re.search(r"(\w)(.\d)","as.21") m <re.Match?object;?span=(1,?4),?match='s.2'>m.group()? s.2 m.group(0) 's.2' m.group(1)?#根據要求返回特定子組 's' m.group(2) '.2' m.start() 1 m.start(2)#第二個組匹配的索引位置 2 m.groups() ('s',?'.21') m.span() (1, 5)2) match()
描述:必須從字符串開頭匹配,同樣返回的是Match對象,對應的方法與search方法一致,此處不再贅述。
語法:re.match(pattern, string, flags=0)
pattern ? 匹配的正則表達式
string ?要匹配的字符串
flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等
3) fullmatch()
描述:整個字符串與正則完全匹配,同樣返回的是Match對象,對應的方法與search方法一致,此處不再贅述
語法:(pattern, string, flags=0)
pattern ? 匹配的正則表達式
string ?要匹配的字符串
flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等
2、查找多個匹配項
講完查找一項,現在來看看查找多項吧,查找多項函數主要有:findall函數 與 finditer函數:
1)findall: 從字符串任意位置查找,返回一個列表
2)finditer:從字符串任意位置查找,返回一個迭代器
兩個函數功能基本類似,只不過一個是返回列表,一個是返回迭代器。我們知道列表是一次性生成在內存中,而迭代器是需要使用時一點一點生成出來的,運行時占用內存更小。如果存在大量的匹配項的話,建議使用finditer函數,一般情況使兩個函數不會有太大的區別。
1)findall
描述:返回字符串里所有不重疊的模式串匹配,以字符串列表的形式出現。
語法:re.findall(pattern, string, flags=0)
pattern ? 匹配的正則表達式
string ?要匹配的字符串
flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等
2)finditer()
描述:返回一個產生匹配對象實體的迭代器,能產生字符串中所有RE模式串的非重疊匹配。
語法:re.finditer(pattern, string, flags=0)
pattern ? 匹配的正則表達式
string ?要匹配的字符串
flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區分大小寫,多行匹配等等
3、匹配項分割
1)split()
描述:split能夠按照所能匹配的字串將字符串進行切分,返回切分后的字符串列表
形式.切割功能非常強大
語法:re.split(pattern, string, maxsplit=0, flags=0)
pattern:匹配的字符串
string:需要切分的字符串
maxsplit:分隔次數,默認為0(即不限次數)
flags:標志位,用于控制正則表達式的匹配方式,flags表示模式,就是上面我們講解的常量!支持正則及多個字符切割。
正則表達的分割函數與字符串的分割函數一樣,都是split,只是前面的模塊不一樣,正則表達的函數為, ,用pattern分開string,maxsplit表示最多進行分割次數,
re.split(r";","var?A;B;C:integer;") ['var?A',?'B',?'C:integer',?'']re.split(r"[;:]","var?A;B;C:integer;") ['var?A',?'B',?'C',?'integer',?'']text?=?'chi13putao14butu520putaopi666' pattern?=?r'\d+' re.split(pattern,text) ['chi',?'putao',?'butu',?'putaopi',?'']line?=?'aac?bba?ccd;dde???eef,fff' #單字符切割 re.split(r';',line) ['aac?bba?ccd',?'dde???eef,fff']#兩個字符以上切割需要放在?[?]?中 re.split(r'[;,]',line) ['aac?bba?ccd',?'dde???eef',?'fff'] #所有空白字符切割 re.split(r'[;,\s]',line) ['aac',?'bba',?'ccd',?'dde',?'',?'',?'eef',?'fff'] #使用括號捕獲分組,默認保留分割符 re.split(r'([;])',line) ['aac?bba?ccd',?';',?'dde???eef,fff']#不想保留分隔符,以(?:...)的形式指定 re.split(r'(?:[;])',line) ['aac?bba?ccd',?'dde???eef,fff'] #不想保留分隔符,以(?:...)的形式指定 re.split(r'(?:[;,\s])',line) ['aac',?'bba',?'ccd',?'dde',?'',?'',?'eef',?'fff']注意:str模塊也有一個split函數 ,那這兩個函數該怎么選呢?str.split函數功能簡單,不支持正則分割,而re.split支持正則。關于二者的速度如何?來實際測試一下,在相同數據量的情況下使用re.split函數與str.split函數執行次數 與 執行時間 對比圖:
#運行時間統計 只計算了程序運行的CPU時間,且每次測試時間有所差異 import time #統計次數 n?=?1000 ``start_t = time.perf_counter() for i in range(n): re.split(r';',line)##re.split end_t = time.perf_counter() print ('re.split: '+str(round(1000000*(end_t-start_t),2)))start_t?=?time.perf_counter() for i in range(n): line.split(';')##str.split end_t = time.perf_counter() print ('str.split: '+str(round(1000000*(end_t-start_t),2)))通過上圖對比發現,5000次循環以內str.split函數和re.split函數執行時間差異不大,而循環次數1000次以上后str.split函數明顯更快,而且次數越多差距越大!
所以結論是,在不需要正則支持的情況下使用str.split函數更合適,反之則使用re.split函數。具體執行時間與測試數據有關!且每次測試時間有所差異
?
4、匹配項替換
替換主要有sub函數與subn函數兩個函數,他們功能類似,不同點在于sub只返回替換后的字符串,subn返回一個元組,包含替換后的字符串和替換次數。
python 里面可以用 replace 實現簡單的替換字符串操作,如果要實現復雜一點的替換字符串操作,需用到正則表達式。
re.sub用于替換字符串中匹配項,返回一個替換后的字符串,subn方法與sub()相同, 但返回一個元組, 其中包含新字符串和替換次數。
sub是substitute表示替換。
1)sub
描述:它的作用是正則替換。
語法:re.sub(pattern, repl, string, count=0, flags=0)
pattern:該參數表示正則中的模式字符串;
repl:repl可以是字符串,也可以是可調用的函數對象;如果是字符串,則處理其中的反斜杠轉義。如果它是可調用的函數對象,則傳遞match對象,并且必須返回要使用的替換字符串
string:該參數表示要被處理(查找替換)的原始字符串;
count:可選參數,表示是要替換的最大次數,而且必須是非負整數,該參數默認為0,即所有的匹配都會被替換;
flags:可選參數,表示編譯時用的匹配模式(如忽略大小寫、多行模式等),數字形式,默認為0。
把字符串?aaa34bvsa56s中的數字替換為?*號
import?re re.sub('\d+','*','aaa34bvsa56s')#連續數字替換 'aaa*bvsa*s' re.sub('\d','*','aaa34bvsa56s')#每個數字都替換一次 'aaa**bvsa**s' #只天換一次count=1,第二次的數字沒有被替換 re.sub('\d+','*','aaa34bvsa56s',count=1) 'aaa*bvsa56s' 把chi13putao14butu520putaopi666中的數字換成... text?=?'chi13putao14butu520putaopi666' pattern?=?r'\d+' re.sub(pattern,'...',text) 'chi...putao...butu...putaopi...'關于第二個參數的用法,我們可以看看下面的內容
#定義一個函數 def?refun(repl):print(type(repl))return('...') re.sub('\d+',refun,'aaa34bvsa56s') <class 're.Match'> <class 're.Match'> 'aaa...bvsa...s'從上面的例子看來,似乎沒啥區別
原字符串中有多少項被匹配到,這個函數就會被調用幾次。
至于傳進來的這個match對象,我們調用它的.group()方法,就能獲取到被匹配到的內容,如下所示:
def?refun(repl):print(type(repl),repl.group())return('...') re.sub('\d+',refun,'aaa34bvsa56s') <class 're.Match'> 34 <class 're.Match'> 56 Out[113]: 'aaa...bvsa...s'這個功能有什么用呢?我們設想有一個字符串moblie18123456794num123,這個字符串中有兩段數字,并且長短是不一樣的。第一個數字是11位的手機號。我想把字符串替換為:moblie[隱藏手機號]num***。不是手機號的數字,每一位數字逐位替換為星號。
def?refun(repl):if?len(repl.group())?==?11:return '[隱藏手機號]'else:return '*' * len(repl.group()) re.sub('\d+',?refun,?'moblie18217052373num123') 'moblie[隱藏手機號]num***'?
2)subn
描述:函數與 re.sub函數 功能一致,只不過返回一個元組 (字符串, 替換次數)。
語法:re.subn(pattern, repl, string, count=0, flags=0)
參數:同re.sub
re.subn('\d+','*','aaa34bvsa56s')#連續數字替換 ('aaa*bvsa*s',?2)re.subn('\d','*','aaa34bvsa56s')#每個數字都替換一次 ('aaa**bvsa**s',?4)text?=?'chi13putao14butu520putaopi666' pattern?=?r'\d+' re.subn(pattern,'...',text) ('chi...putao...butu...putaopi...',?4)?
5、編譯正則對象
描述:將正則表達式的樣式編譯為一個正則表達式對象(正則對象),可以用于匹配
語法:re.compile(pattern,?flags=0)
參數:
pattern:該參數表示正則中的模式字符串;
flags:可選參數,表示編譯時用的匹配模式(如忽略大小寫、多行模式等),數字形式,默認為0。
如果需要多次使用這個正則表達式的話,使用re.compile()和保存這個正則對象以便復用,可以讓程序更加高效。
6、轉義函數
re.escape(pattern) 可以轉義正則表達式中具有特殊含義的字符,比如:. 或者 * re.escape(pattern) 看似非常好用省去了我們自己加轉義,但是使用它很容易出現轉義錯誤的問題,所以并不建議使用它轉義,而建議大家自己手動轉義!
7、緩存清除函數
re.purge()函數作用就是清除正則表達式緩存,清除后釋放內存。
六、異常模塊
re模塊還包含了一個正則表達式的編譯錯誤,當我們給出的正則表達式是一個無效的表達式(就是表達式本身有問題)時,就會raise一個異常。
?
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載機器學習的數學基礎專輯 本站知識星球“黃博的機器學習圈子”(92416895) 本站qq群704220115。 加入微信群請掃碼:總結
以上是生活随笔為你收集整理的【Python基础】Python正则表达式入门到入魔的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爱奇艺视频播放怎么开加速
- 下一篇: 【Python基础】快速入门Python