<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python正則表達式入門到入魔

          共 20714字,需瀏覽 42分鐘

           ·

          2021-02-09 20:56


          關于正則表達式,很多人認為,使用的時候查詢下就可以,沒必要深入學習,但是知識與應用永遠都是螺旋辯證的關系,有需要查詢也無可厚非,但是先掌握知識,可以讓應用更創(chuàng)新,更深入,超越他人,必須要先掌握大量的深刻的知識。

          如果正則學的好,你會發(fā)現它功能強大、妙用無窮。對于很多實際工作來講,正則表達式簡直是靈丹妙藥,能夠成百倍地提高開發(fā)效率和程序質量。

          但是學習正則并非易事:知識點瑣碎、記憶點多、符號亂,難記憶、難描述、廣而深且,為此,我做了一個全面的整理,希望能幫助到大家。

          正則表達式是一個復雜的主題,因此本文非常長,作者能力有限,錯誤之處,懇請指正,為了方便閱讀,先看文章目錄:


          一、起源發(fā)展

          ? 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()

          ??? ?2match()

          ??? ?3fullmatch()

          ? 2、查找多個匹配項

          ???? 1)findall()

          ??? ?2)finditer()

          ??3、匹配項分割

          ??????1)split()

          ??4、匹配項替換

          ??? ?1)sub()

          ??? ?2)subn()

          ??5、編譯正則對象

          ??6、轉義函數

          ??7、緩存清除函數

          六、異常模塊



          一、起源發(fā)展

          我們在學習一門技術的時候有必要簡單的了解其起源與發(fā)展過程,這對學習知識有一定的幫助。

          1、20世紀40年代

          正則表達式最初的想法來自兩位神經學家:沃爾特·皮茨與麥卡洛克,他們研究出了一種用數學方式來描述神經網絡的模型。

          2、20世紀50年代

          一位名叫Stephen Kleene的數學科學家發(fā)表了一篇題目是《神經網事件的表示法》的論文,利用稱之為正則集合的數學符號來描述此模型,引入了正則表達式的概念。正則表達式被作為用來描述其稱之為“正則集的代數”的一種表達式,因而采用了“正則表達式”這個術語。

          3、20世紀60年代

          C語言之父、UNIX之父肯·湯普森把這個“正則表達式”的理論成果用于做一些搜索算法的研究,他描述了一種正則表達式的編譯器,于是出現了應該算是最早的正則表達式的編譯器qed(這也就成為后來的grep編輯器)。

          Unix使用正則之后,正則表達式不斷的發(fā)展壯大,然后大規(guī)模應用于各種領域,根據這些領域各自的條件需要,又發(fā)展出了許多版本的正則表達式,出現了許多的分支。我們把這些分支叫做“流派”。

          4、20世紀80年代

          Perl語言誕生了,它綜合了其他的語言,用正則表達式作為基礎,開創(chuàng)了一個新的流派,Perl流派。之后很多編程語言如:Python、Java、Ruby、.Net、PHP等等在設計正則式支持的時候都參考Perl正則表達式。到這里我們也就知道為什么眾多編程語言的正則表達式基本一樣,因為他們都師從Perl。注:Perl語言是一種擅長處理文本的語言,Larry在1987年創(chuàng)建,Practical Extraction and Report Language,實用報表提取語言,但因晦澀語法和古怪符號不利于理解和記憶導致很多開發(fā)者并不喜歡。


          二、學習資料

          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?reprint(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可以是任意正則表達式,創(chuàng)建一個正則表達式,匹配A或者B.,任意個正則表達式可以用'|'連接。它也可以在組合(見下列)內使用。掃描目標字符串時,'|'分隔開的正則樣式從左到右進行匹配。當一個樣式完全匹配時,這個分支就被接受。意思就是,一旦A匹配成功,B就不再進行匹配,即便它能產生一個更好的匹配?;蛘哒f'|'操作符絕不貪婪。如果要匹配'|'字符,使用\|,或者把它包含在字符集里,比如[|]。

          -

          可以表示字符范圍,通過用'-'將兩個字符連起來。比如[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模式也匹配換行后的首個符號。

          #匹配字符串開頭是否包含emrTre.findall(r'^[emrT]','He?was?carefully')[]#匹配開頭是否包含emrHre.findall(r'^[emrH]','He?was?carefully')?['H']#匹配開頭是否包含2個以上的are.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 標記被設置的話,詞的邊界是由當前語言區(qū)域設置決定的,\b 表示退格字符,以便與Python字符串文本兼容。

          \B

          匹配空字符串,但不能在詞的開頭或者結尾。意思就是r'py\B'匹配'python','py3','py2', 但不匹配'py','py.', 或者'py!'.\B是\b的取非,所以Unicode樣式的詞語是由Unicode字母,數字或下劃線構成的,雖然可以用ASCII標志來改變。如果使用了LOCALE標志,則詞的邊界由當前語言區(qū)域設置。


          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]?,還有很多其他字符,比如不同語言排版規(guī)則約定的不換行空格)。如果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標記,就匹配當前語言區(qū)域的數字和字母和下劃線。

          #未設置,全部匹配
          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旗標,則會匹配在當前區(qū)域設置中不是字母數字又不是下劃線的字符。

          re.findall(r'\W+','chi小伍哥putao14butu真putaopi帥666',?re.ASCII)['小伍哥',?'真',?'帥']

          [\b]

          它只在字符集合內表示退格,比如[\b]

          ?


          5、前后查找

          import?refrom?urllib?import?request#爬蟲爬取百度首頁內容data=request.urlopen("http://www.baidu.com/").read().decode()

          #分析網頁,確定正則表達式pat=r'(.*?)'result=re.search(pat,data)print(result) 1358,?1382),?match='百度一下,你就知道'>result.group() # 百度一下,你就知道'百度一下,你就知道'

          在爬取的HTML頁面中,要匹配出一對標簽之間的文本,'</span><span style="color: rgb(255, 0, 0);color: #ff0000;font-size: 10pt;font-family: 'Microsoft YaHei', 微軟雅黑, sans-serif;">welcome to my page</span><span style="color: rgb(51, 102, 255);color: #3366ff;font-size: 10pt;font-family: 'Microsoft YaHei', 微軟雅黑, sans-serif;">',之間的文本,方法如下:

          #正則表達式re.findall(r'<[Tt][Ii][Tt][Ll][Ee]>.*?','welcome?to?my?page')#結果['welcome?to?my?page']

          分析:<[Tt][Ii][Tt][Ll][Ee]>表示不區(qū)分大小寫,這個模式匹配到了title標簽以及它們之間的文本,但是并不完美,因為我們只想要title標簽之間的文本,而不包括標簽本身。為了解決類似的問題,我們就需要用到前后查找的方法。

          1)向前查找

          向前查找指定了一個必須匹配但不在結果中返回的模式。向前查找實際上就是一個子表達式,它以?=開頭,需要匹配的文本跟在=的后面。


          #使用向前查找re.findall(r'<[Tt][Ii][Tt][Ll][Ee]>.*?(?=)','welcome to my page')#返回結果['welcome to my page'</span>]</span></code><code><span id="go7utgvlrp"    class="code-snippet_outer"><span id="go7utgvlrp"    class="code-snippet__comment">#可以看大,后面?zhèn)€已經沒有了,但是前面?zhèn)€還在,那要用到我們的向后查找#看一個提取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]>).*?(?=)','welcome to my page')#結果['welcome to my page']

          分析:從結果可以看出,問題完美的解決了。(?<=<[Tt][Ii][Tt][Ll][Ee]>)是一個向后操作,它匹配但不消費它,(?=</[Tt][Ii][Tt][Ll][Ee]>)是一個向前操作,它匹配但不消費它。最終返回的匹配結果只包含了標簽之間的文本了。

          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(?

          結果:I paid $30 for 【10】 apples, 【15】 oranges, and 【10】pears. I saved $5 on this order.

          分析:(?

          5)小結

          前后查找的操作符:

          (?=)

          正向前查找

          (?!)

          負向前查找

          (?<=)

          正向后查找

          (?

          負向后查找

          有了前后查找,就可以對最終的匹配結果包含哪些內容做出精確的控制。前后查找操作使我們可以利用子表達式來指定文本匹配操作發(fā)生的位置,并收到只匹配不消費的效果。


          6、回溯引用

          回溯引用是正則表達式內的一個“動態(tài)”的正則表達式,讓你根據實際的情況變化進行匹配。說白了,就是一個根據你的需要,動態(tài)變化的表達式。

          舉個栗子:

          你原本要匹配

          之間的內容,現在你知道HTML有多級標題,你想把每一級的標題內容都提取出來。你也許會這樣寫:

          p?=?r".*?"

          這樣一來,你就可以將HTML頁面內所有的標題內容全部匹配出來。即

          的內容都可以被提取出來。但是我們之前說過,寫正則表達式困難的不是匹配到想要的內容,而是盡可能的不匹配到不想要的內容。在這個例子中,很有可能你就會被下面這樣的用例玩壞。

          比方說

          <h1>hello?worldh3>

          發(fā)現后面的了嗎?我們不管是怎么寫出來這樣的標題的,但實實在在的是我們的正則表達式同樣會把這里面的hello world匹配出來。這時候就是回溯引用的重要作用。下面就是一個示例:

          import?rekey?=?r"

          hello?world

          "
          p1?=?r".*?"pattern1?=?re.compile(p1)m1?=?re.search(pattern1,key)
          print m1.group(0)#這里是會報錯的,因為匹配不到,你如果將源字符串改成
          #結尾就能看出效果

          看到\1了嗎?原本那個位置應該是[1-6],但是我們寫的是\1,我們之前說過,轉義符\干的活就是把特殊的字符轉成一般的字符,把一般的字符轉成特殊字符。普普通通的數字1被轉移成什么了呢?在這里1表示第一個子表達式,也就是說,它是動態(tài)的,是隨著前面第一個子表達式的匹配到的東西而變化的。比方說前面的子表達式內是[1-6],在實際字符串中找到了1,那么后面的\1就是1,如果前面的子表達式在實際字符串中找到了2,那么后面的\1就是2。

          類似的,\2,\3,....就代表第二個第三個子表達式。

          所以回溯引用是正則表達式內的一個“動態(tài)”的正則表達式,讓你根據實際的情況變化進行匹配。


          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編碼支持的字符)。注意:這只對字符串匹配模式有效,對字節(jié)匹配模式無效。


          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

          作用: 由當前語言區(qū)域決定 \w, \W, \b, \B 和大小寫敏感匹配,這個標記只能對byte樣式有效,該標記官方已經不推薦使用,因為語言區(qū)域機制很不可靠,它一次只能處理一個 "習慣”,而且只對8位字節(jié)有效。


          7、UNICODE

          語法: re.UNICODE 或簡寫為 re.U

          作用: 與 ASCII常量類似,匹配unicode編碼支持的字符,但是Python3默認字符串已經是Unicode,所以顯得有點多余。


          8、DEBUG

          語法: re.DEBUG

          作用: 顯示編譯時的debug信息。

          re.findall(r'tm','小伍哥tm真帥',re.DEBUG)
          LITERAL 116LITERAL 109
          0. INFO 10 0b11 2 2 (to 11) prefix_skip 2 prefix [0x74, 0x6d] ('tm') overlap [0, 0]11: LITERAL 0x74 ('t')13. LITERAL 0x6d ('m')15. SUCCESSOut[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,發(fā)現沒 b 了,糟糕!趕緊回溯!所以第一個案例沒禁止,能匹配,第二個案例有禁止,系統(tǒng)報錯

          • 返回 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函數: 將正則表達式的樣式編譯為一個 正則表達式對象

          print(dir(re))[......?'compile',?'copyreg',?'enum',?'error',?'escape',?'findall',?'finditer',?'fullmatch',?'functools',?'match',?'purge',?'search',?'split',?'sre_compile',?'sre_parse',?'sub',?'subn',?'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 標志位,用于控制正則表達式的匹配方式

          re.search(r"(\w)(.\d)","as.21").group()'s.2'

          假設返回的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")m1,?4),?match='s.2'>
          m.group()?s.2m.group(0)'s.2'm.group(1)?#根據要求返回特定子組's'm.group(2)'.2'm.start()1m.start(2)#第二個組匹配的索引位置2m.groups()('s',?'.21')m.span()(1, 5)

          2) match()

          描述:必須從字符串開頭匹配,同樣返回的是Match對象,對應的方法與search方法一致,此處不再贅述。

          語法:re.match(pattern, string, flags=0)

          • pattern ? 匹配的正則表達式

          • string ?要匹配的字符串

          • flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等

          #從頭開始匹配,返回一個數字,發(fā)現報錯,無法返回,因為不是數字開頭的
          text?=?'chi13putao14butu520putaopi666'pattern?=?r'\d+'re.match(pattern,text).group()AttributeError:?'NoneType'?object?has?no?attribute?'group'
          #從頭開始匹配,返回一個單詞,正常返回了開頭的單詞text?=?'chi13putao14butu520putaopi666'pattern?=?r'[a-z]+'re.match(pattern,text).group()'chi'

          3) fullmatch()

          描述:整個字符串與正則完全匹配,同樣返回的是Match對象,對應的方法與search方法一致,此處不再贅述

          語法:(pattern, string, flags=0)

          • pattern ? 匹配的正則表達式

          • string ?要匹配的字符串

          • flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等

          #必須要全部符合條件才能匹配re.fullmatch(r'[a-z]+','chiputao14').group()AttributeError:?'NoneType'?object?has?no?attribute?'group'
          re.fullmatch(r'[a-z]+','chiputao').group()'chiputao'

          2、查找多個匹配項

          講完查找一項,現在來看看查找多項吧,查找多項函數主要有:findall函數finditer函數

          1)findall: 從字符串任意位置查找,返回一個列表

          2)finditer:從字符串任意位置查找,返回一個迭代器

          兩個函數功能基本類似,只不過一個是返回列表,一個是返回迭代器。我們知道列表是一次性生成在內存中,而迭代器是需要使用時一點一點生成出來的,運行時占用內存更小。如果存在大量的匹配項的話,建議使用finditer函數,一般情況使兩個函數不會有太大的區(qū)別。

          1)findall

          描述:返回字符串里所有不重疊的模式串匹配,以字符串列表的形式出現。

          語法:re.findall(pattern, string, flags=0)

          • pattern ? 匹配的正則表達式

          • string ?要匹配的字符串

          • flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等

          import?retext????=?'Python太強大,我愛學Python'pattern?=?'Python're.findall(pattern,text)['Python',?'Python']#找出下列字符串中的數字text?=?'chi13putao14butu520putaopi666'#\d+表示匹配任意數字pattern?=?r'\d+'re.findall(pattern,text)['13',?'14',?'666',?'520']?
          text?=?'ab-abc-a-cccc-d-aabbcc'pattern?=?'ab*'re.findall(pattern,text)['ab',?'ab',?'a',?'a',?'abb']
          #找到所有副詞'''findall()?匹配樣式?所有?的出現,不僅是像 search()?中的第一個匹配。比如,如果一個作者希望找到文字中的所有副詞,他可能會按照以下方法用?findall()'''text = "He was carefully disguised but captured quickly by police."re.findall(r"\w+ly", text)['carefully',?'quickly']

          2)finditer()

          描述:返回一個產生匹配對象實體的迭代器,能產生字符串中所有RE模式串的非重疊匹配。

          語法:re.finditer(pattern, string, flags=0)

          • pattern ? 匹配的正則表達式

          • string ?要匹配的字符串

          • flags ? 標志位,用于控制正則表達式的匹配方式,如:是否區(qū)分大小寫,多行匹配等等

          m?=?re.finditer("Python","Python非常強大,我愛學習Python")m?for?i?in?m:    print(i.group(0))PythonPython#找到所有副詞和位置
          '''如果需要匹配樣式的更多信息,?finditer()?可以起到作用,它提供了?匹配對象?作為返回值,而不是字符串。繼續(xù)上面的例子,如果一個作者希望找到所有副詞和它的位置,可以按照下面方法使用?finditer()'''text?=?"He?was?carefully?disguised?but?captured?quickly?by?police."for?m?in?re.finditer(r"\w+ly",?text):????print('%02d-%02d:?%s'?%?(m.start(),?m.end(),?m.group(0)))
          07-16: carefully40-47: quic

          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函數執(zhí)行次數 與 執(zhí)行時間 對比圖:

          #運行時間統(tǒng)計 只計算了程序運行的CPU時間,且每次測試時間有所差異import time#統(tǒng)計次數n?=?1000``start_t = time.perf_counter()for i in range(n):     re.split(r';',line)##re.splitend_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.splitend_t = time.perf_counter()print ('str.split: '+str(round(1000000*(end_t-start_t),2)))

          通過上圖對比發(fā)現,5000次循環(huán)以內str.split函數和re.split函數執(zhí)行時間差異不大,而循環(huán)次數1000次以上后str.split函數明顯更快,而且次數越多差距越大!

          所以結論是,在不需要正則支持的情況下使用str.split函數更合適,反之則使用re.split函數。具體執(zhí)行時間與測試數據有關!且每次測試時間有所差異

          ?

          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?rere.sub('\d+','*','aaa34bvsa56s')#連續(xù)數字替換'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'

          從上面的例子看來,似乎沒啥區(qū)別

          原字符串中有多少項被匹配到,這個函數就會被調用幾次。

          至于傳進來的這個match對象,我們調用它的.group()方法,就能獲取到被匹配到的內容,如下所示:

          def?refun(repl):    print(type(repl),repl.group())    return('...')re.sub('\d+',refun,'aaa34bvsa56s')<class 're.Match'> 34<class 're.Match'> 56Out[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')#連續(xù)數字替換('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。

          prog???=?re.compile(pattern)result?=?prog.match(string)等價于result?=?re.match(pattern,?string)

          如果需要多次使用這個正則表達式的話,使用re.compile()和保存這個正則對象以便復用,可以讓程序更加高效。


          6、轉義函數

          re.escape(pattern) 可以轉義正則表達式中具有特殊含義的字符,比如:. 或者 * re.escape(pattern) 看似非常好用省去了我們自己加轉義,但是使用它很容易出現轉義錯誤的問題,所以并不建議使用它轉義,而建議大家自己手動轉義!

          7、緩存清除函數

          re.purge()函數作用就是清除正則表達式緩存,清除后釋放內存。

          六、異常模塊

          re模塊還包含了一個正則表達式的編譯錯誤,當我們給出的正則表達式是一個無效的表達式(就是表達式本身有問題)時,就會raise一個異常。

          ?


          推薦閱讀:

          萬字長文詳解|Python庫collections,讓你擊敗99%的Pythoner

          Python初學者必須吃透這69個內置函數!

          Python字典詳解-超級完整版

          全面理解Python集合,17個方法全解,看完就夠了

          掃描關注本號↓

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人人操人人干在线观看 | 日韩免费福利视频 | www.色色五月天 | 青草午夜视频 | 草逼视频链接 |