1. 程式人生 > >第一章:正則表示式

第一章:正則表示式

目錄

  一. 正則表示式

  二. 特殊的元字元

  三. python3的re模組方法

  四. python3的re模組練習

  五. 第一章課後練習題

  六. re模組綜合應用之計算器

一. 正則表示式

  正則表示式是由一堆字元和特殊符號組成的字串。它可以為我們提供高階的文字搜尋,匹配,替換功能。當然,正則表示式也不是python獨有的一種模式,而是凌駕於語言之上的一種跨平臺的通用標準。當我們學會了正則表示式之後,將會能夠更加容易的處理我們的文字和資料。讓我們開始正則之旅吧。

二. 特殊的元字元

  正則表示式本質上就是一堆字串,只不過構成這個字串的每一個字元都是有特別意義的,我們想要去真正的去了解正則表示式,就必須要清楚的記得特殊字元的含義。下圖是python核心程式設計中關於元字元的描述。雖然有很多,但是你一定要背會(當然在學的過程中你會發現其實也沒有那麼難背,用著用著就記住了,但是還是提醒,不管是什麼方法,一定要記住)。

  

  

   在我們真正開始正則表示式之前,我們首先要了解一個工具,那就是python的re模組,快速的瞭解,只需要知道通過這個模組我們可以檢視寫出來的正則表示式是否準確就可以了。之後我們會再去詳細的檢視,使用方法如下:

>>> re.search('^s.*$', 'sllsljegleg')# 第一個引數:就是我們寫出來的正則表示式(從表面上看就是字串)第二個引數:就是我們匹配的字串,如果匹配到了就返回值
<_sre.SRE_Match object; span=(0, 11), match='sllsljegleg'> # 最後match後面的就是我們匹配到的字串

>>> re.search('^s.*$', 'llsljegleg') # 如果沒有匹配到就沒有顯示
>>>

第一類: 位置匹配

  位置匹配就是專門用來描述位置的元字元,有四個: 【^,$, \A,\Z】(注意是有大小寫之分的),^ 和\A都表示字串的開頭,$ 和\Z都表示字串的結尾,為什麼會有兩個元字元去表示同一個事物呢?這是因為在一些國際鍵盤上是沒有脫字元的,所以設計的時候又設計了\A和\Z來表示。

>>> re.search('^From','From to China')  # 以From開頭的字串
<_sre.SRE_Match object; span=(0, 4), match='From'>


>>> re.search('/bin/tcsh$', 'python /bin/tcsh') # 以/bin/tcsh結尾的字串  
<_sre.SRE_Match object; span=(7, 16), match='/bin/tcsh'>

>>> re.search('^Subject:hi$', 'Subject:hi')  # 如果前面有脫字元,後面美元符,就代表只匹配裡面的值,此例中就是隻匹配Subject:hi
<_sre.SRE_Match object; span=(0, 10), match='Subject:hi'>
>>>

  需要匹配【$】和脫字元【^】的時候怎麼做呢,通過【\】斜槓進行轉義

>>> re.search('\$', '$')   通過\轉義就可以匹配到$ 符了
<_sre.SRE_Match object; span=(0, 1), match='$'>
>>> re.search('\^\$', 'hello ^$')
<_sre.SRE_Match object; span=(6, 8), match='^$'>
>>>

  【\A, \Z】的用法和^$的用法是一樣的

>>> re.search('\AFrom','From to China')  # 以From開頭的字串
<_sre.SRE_Match object; span=(0, 4), match='From'>


>>> re.search('/bin/tcsh\Z', 'python /bin/tcsh') # 以/bin/tcsh結尾的字串  
<_sre.SRE_Match object; span=(7, 16), match='/bin/tcsh'>

>>> re.search('\ASubject:hi\Z', 'Subject:hi')  # 如果前面有脫字元,後面美元符,就代表只匹配裡面的值,此例中就是隻匹配Subject:hi
<_sre.SRE_Match object; span=(0, 10), match='Subject:hi'>
>>>
\A和\Z的使用方法

  【\b】匹配任何單詞邊界

>>> s = 'this island is beautiful'
>>> import re
>>> re.search(r'\bis', s)    # 此時我們會發現匹配到了索引為5,7的字元,也就是說island這個前面的is匹配到了
<_sre.SRE_Match object; span=(5, 7), match='is'>
>>> re.search(r'\bis\b', s)  # 如果加上\b界定上單詞邊界之後就會之匹匹厄後面的is了,因為前面的is並不是一個單詞
<_sre.SRE_Match object; span=(12, 14), match='is'>
>>>

第二類: 重複匹配

  重複匹配就是將之前匹配的正則表示式重新匹配多少次。重複匹配是正則表示式中基本上最常用的模式。

  【*】匹配前面正則表示式0次或者多次

>>> re.search('\d\d*', '1')  # 第一個\d匹配一個數字,第二個\d本來也是匹配一個數字,但是後面加上了一個*,代表前面可以不匹配,也可以匹配一次或者多次
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.search('\d\d*', '12')   # 這個就是第二個\d匹配了1次
<_sre.SRE_Match object; span=(0, 2), match='12'>
>>> re.search('\d\d*', '123')  # 這個就是第二個\d匹配了2次
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>>

  【+】匹配前面正則表示式1次或者多次

>>> re.search('\d\d+', '1')   # 第一個\d匹配到了1,但是第二個\d後面有+代表最少匹配一次,但是字串沒有數字了,所以就沒有匹配到值
>>> re.search('\d\d+', '12')  # 這個代表第二個\d匹配了一次
<_sre.SRE_Match object; span=(0, 2), match='12'>
>>> re.search('\d\d+', '123')  # 這個代表第二個\d匹配了兩次
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>>

  【?】匹配前面正則表示式0次或者1次

>>> re.search('\d\d?', '1')  # 第一個\d匹配到了1,但是第二個\d後面有?說明可以匹配0次,也就是不匹配
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.search('\d\d?', '12')  # 第二個\d匹配了1次
<_sre.SRE_Match object; span=(0, 2), match='12'>
>>> re.search('\d\d?', '123')  # 雖然匹配到了,但是發現匹配到的值還是12,最多隻能匹配一次
<_sre.SRE_Match object; span=(0, 2), match='12'>
>>>

  【{n}】匹配前面正則表示式n次

>>> re.search('\d\d{2}', '12')  
>>> re.search('\d\d{2}', '1')
>>> re.search('\d\d{2}', '123')    # {2}代表前面的\d必須要匹配兩次,所以只能匹配到123
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> re.search('\d\d{2}', '1234')
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>>

  【{n, m}】匹配前面正則表示式n到m次

>>> re.search('\d\d{2,4}', '12')
>>> re.search('\d\d{2,4}', '123')  # {2,4}代表匹配前面\d2次到4次,因此我們可以發現最少要匹配兩次,最多要匹配4次
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> re.search('\d\d{2,4}', '1234')
<_sre.SRE_Match object; span=(0, 4), match='1234'>
>>> re.search('\d\d{2,4}', '12345')
<_sre.SRE_Match object; span=(0, 5), match='12345'>
>>> re.search('\d\d{2,4}', '123456')
<_sre.SRE_Match object; span=(0, 5), match='12345'>
>>>

  重複匹配的例子

# 1. 匹配字元d或b之後跟著一個o最後可以跟著一個t也可以不跟著一個t的例子
>>> re.search('[db]ot?', 'do')
<_sre.SRE_Match object; span=(0, 2), match='do'>
>>> re.search('[db]ot?', 'dot')
<_sre.SRE_Match object; span=(0, 3), match='dot'>
>>> re.search('[db]ot?', 'bo')
<_sre.SRE_Match object; span=(0, 2), match='bo'>
>>> re.search('[db]ot?', 'bot')
<_sre.SRE_Match object; span=(0, 3), match='bot'>
>>>

# 2. 匹配9-16位的信用卡號
>>> re.search('[0-9]{9, 16}', '1234567890')  # 注意不能加空格
>>> re.search('[0-9]{9,16}', '1234567890')
<_sre.SRE_Match object; span=(0, 10), match='1234567890'>
>>>

# 3. 匹配全部有效的html標籤
>>> re.search('</?[^>]+>', '</>')  #/?代表/可以不出現,代表的是開頭的標籤,也可以出現1次,代表的是結尾的標籤,[^>]代表的是除了>的任何字元,後面跟上+,也就是說除了>的任何字元出現一次或者多次
<_sre.SRE_Match object; span=(0, 3), match='</>'>
>>> re.search('</?[^>]+>', '<hello>')
<_sre.SRE_Match object; span=(0, 7), match='<hello>'>
>>>

第三類: 範圍匹配

  範圍匹配是通過一個或者一組特殊的字元代表一個範圍的資料。例如:【\d】代表的是0-9的數字數字。【大寫字母的都是與之相反的】

     【.】點代表的是除了換行符之外的任意的字元的匹配

>>> re.search('f.o', 'fao')    # 匹配字母f和o之間的任何一個字元
<_sre.SRE_Match object; span=(0, 3), match='fao'>
>>> re.search('f.o', 'f#o')
<_sre.SRE_Match object; span=(0, 3), match='f#o'>
>>> re.search('f.o', 'f9o')
<_sre.SRE_Match object; span=(0, 3), match='f9o'>
>>> re.search('..', 'js')      # 匹配任意的兩個字元
<_sre.SRE_Match object; span=(0, 2), match='js'>  
>>> re.search('..', 'ss')
<_sre.SRE_Match object; span=(0, 2), match='ss'>
>>>

  【\d】表示0-9的任何十進位制數字

>>> re.search('\d', '1')   # 匹配0-9的任何一個字元
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.search('\d', '2')
<_sre.SRE_Match object; span=(0, 1), match='2'>
>>> re.search('\d', 'z')   # z不是0-9所以匹配不到
>>>

  【\w】字母數字下劃線 相當於[a-zA-0-9_]的縮寫

>>> re.search('\w', '1')  # 字母數字下滑先都可以匹配到,而且也只是匹配一個字元
<_sre.SRE_Match object; span=(0, 1), match='1'>
>>> re.search('\w', 'a')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('\w', '_')
<_sre.SRE_Match object; span=(0, 1), match='_'>
>>> re.search('\w', '#')  # 因為#不在範圍之內,所以沒有匹配到
>>>

  \d和\w的一些例子

#1.   一個數字字母組成的字串和一串由數字組成的字串
>>> re.search('\w+-\d+', 'ab123sejg-123456')   # \w代表數字字母,+代表前面的數字字母可以出現一次或者多次,也就是把字母數字組成的字串表示好了,\d+也是一樣的效果,代表的是一串由數字組成的字串
<_sre.SRE_Match object; span=(0, 16), match='ab123sejg-123456'>
>>>

#2. 以字母開頭,其餘字元是字母或者數字
 >>> re.search('^[A-Za-z]\w*', 'a123lsege')
<_sre.SRE_Match object; span=(0, 9), match='a123lsege'>
>>>

#3. 美國電話號碼的格式 800-555-1212
>>>
>>> re.search('\d{3}-\d{3}-\d{4}', '800-555-1212') # \d代表的是一個數字{3}代表的是前面的數字出現三次,也就是把800給匹配出來了,然後就是單純的一個-,後面的也是一個效果,組合起來就把電話號碼給匹配出來了。
<_sre.SRE_Match object; span=(0, 11), match='800-555-1212'>
>>>

# 4. 電子郵件地址[email protected]
>>> re.search('\[email protected]\w+\.com', '[email protected]') # 首先\w+匹配一個數字字母下劃線的字串,然後一個單純的@,然後又是一個字串, 之後\.因為做了轉義,代表的就是一個單純的.
<_sre.SRE_Match object; span=(0, 10), match='[email protected]'>
>>>

  【\s】匹配空格字元

>>> re.search('of\sthe', 'of the')  # 就是匹配一個空格字元, 無論是換行符,\t\v\f都是可以匹配到的
<_sre.SRE_Match object; span=(0, 6), match='of the'>
>>>

  【[a-b]】中括號代表a-b的一個範圍,代表的是從a-b的字元之間匹配一個字元

  應用一:建立字串

>>> re.search('[A-Z]', 'D')  # 匹配A-Z之間的一個字元,所以D就可以匹配了,之間不能有空格
<_sre.SRE_Match object; span=(0, 1), match='D'>
>>>
>>> re.search('[ab][cd]', 'ac') # 從第一個方框中取出一個值與第二個方框中取出一個值進行組合,注意不能匹配到ab和cd,如果想匹配ab和cd需要通過擇一匹配符號也就是[|]
<_sre.SRE_Match object; span=(0, 2), match='ac'>
>>> re.search('[ab][cd]', 'ad')
<_sre.SRE_Match object; span=(0, 2), match='ad'>
>>> re.search('[ab][cd]', 'ac')
<_sre.SRE_Match object; span=(0, 2), match='ac'>
>>> re.search('[ab][cd]', 'ad')
<_sre.SRE_Match object; span=(0, 2), match='ad'>
>>>

  【[^ a -b]】匹配除了a-b之外的任何一個字元

# 1. z後面跟著任何一個字元然後再跟著一個0-9之間的數字
>>> re.search('z.[0-9]', 'z#0')
<_sre.SRE_Match object; span=(0, 3), match='z#0'>
>>> re.search('z.[0-9]', 'z#1')
<_sre.SRE_Match object; span=(0, 3), match='z#1'>
>>>

# 2. 除了aeiou之外的任何字元
>>> re.search('[^aeiou]', 's')
<_sre.SRE_Match object; span=(0, 1), match='s'>
>>>

# 3. 除了製表符或者換行符之外的任何一個字元
>>> re.search('[^\n\t]', 'aljg')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>>

第四類: 分組匹配

  分組匹配嚴格意義上來講並不是一個匹配的元字元,因為它本身並不會影響匹配的結果,只是會把匹配的結果按照括號分開,然後儲存到一定的位置,便於我們之後使用的。那為什麼要有分組呢?因為在很多的時候我們並不是對於匹配出來的字元感興趣的,有時候我們只是對於匹配字元的某一個塊感興趣,可能還會對這一塊進行一系列的操作。這就需要分組來幫我們做這件事了。

  分組相對來說較為簡單,但是卻相當重要,簡單在於它並不會影響我們的匹配結果,重要在資料匹配之後大部分都要用到這個分組。

  【 () 】,在之後介紹group和groups的時候會提到

>>> import re
>>> re.search('(\d+)([a-z])(\.)(\w+)', '123c.sleg234')  # 這個和下面匹配的結果是一樣的,意思是加上括號分組的時候並不會對匹配的結果產生影響
<_sre.SRE_Match object; span=(0, 12), match='123c.sleg234'>  # 它只是通過分組給我們返回了一系列的資料,我們可以通過group(s)方法獲得而已
>>> re.search('\d+[a-z]\.\w+', '123c.sleg234')         
<_sre.SRE_Match object; span=(0, 12), match='123c.sleg234'>
>>>

第五類:擇一匹配符

  【|】豎槓代表的是從幾個正則表示式中得到一個

>>> re.search('ab|cd', 'ab')  # 從左邊的ab和cd中匹配相應的資料,但是不會匹配ac,這也是和[]的區別
<_sre.SRE_Match object; span=(0, 2), match='ab'>
>>> re.search('ab|cd', 'cd')
<_sre.SRE_Match object; span=(0, 2), match='cd'>
>>> re.search('ab|cd', 'ac')
>>>

第六類:擴充套件表示法

  擴充套件表示法等之後真正用到了再回來看吧

  

三. python3re模組方法

方法一: compile編譯

  程式是我們寫出來的一堆字串,計算機是看不懂的,因此想要讓我們寫出來的程式碼可以正常的計算機中執行,就必須將程式碼塊編譯成位元組碼(也就是程式能夠理解的語言)。而complie編譯就是這個原理,也就是我提前將字串編譯成一個物件,之後你要進行使用的時候不必再進行編譯了,直接呼叫此物件就可以了。對於只調用一次的正則表示式用不用此方法都是可以的,但是對於那些需要呼叫多次的正則表示式而言,提前編譯就能夠大大的提升執行效能。complie方法就是為了做這樣的一件事的。

>>> import re
>>> s = '\d+'     # 這個是自己寫的正則表示式
>>> int_obj = re.compile(s)   # 通過complie方法將s編譯成物件,之後就直接可以通過這個物件直接去呼叫方法,可以節省效能 
>>> print(int_obj.search('123'))
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>> print(int_obj.search('123'))
<_sre.SRE_Match object; span=(0, 3), match='123'>
>>>

方法二:group 和 groups方法

  (1).group和groups方法都是在正則表示式匹配成功之後呼叫的方法,如果沒有匹配成功呼叫此方法會報錯

  (2).group方法會得到一個如下的列表【當前匹配的字串, 分組一,分組二........】沒有分組就只有一個元素

  (3).groups方法會得到一個如下的元組 ( 分組一,分組二......), 沒有分組就是空空列表

  (4).這兩個方法只能是search和match成功匹配到的物件才可以使用。

以美國電話號碼匹配為例s1 = '\d{3}-\d{3}-\d{4}'

(1).沒有分組的情況下

>>> s1 = '\d{3}-\d{3}-\d{4}'  # 美國電話號碼的格式匹配正則表示式
>>> s2 = '(\d{3})-(\d{3})-(\d{4})'    # 將美國的電話號碼進行分組
>>> tel = '099-898-2392'  # 這是一個電話格式
>>> re.search(s1, tel)     # 之前沒有學group的時候我們一直得到的都是下面的這種物件
<_sre.SRE_Match object; span=(0, 12), match='099-898-2392'>
>>> re.search(s1, tel).group()   # 得到匹配的值    
'099-898-2392'
>>> re.search(s1, tel).group(0)  # 和上面的是一樣的,因為沒有分組得到的列表中就只有一個元素
'099-898-2392'
>>> re.search(s1, tel).group(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group
>>> re.search(s1, tel).groups()   # 沒有分組的情況下groups就是空的元組
()
>>>

(2)有分組的情況下

>>> s1 = '\d{3}-\d{3}-\d{4}'  # 美國電話號碼的格式匹配正則表示式
>>> s2 = '(\d{3})-(\d{3})-(\d{4})'    # 將美國的電話號碼進行分組
>>> tel = '099-898-2392'
>>>
>>> print(re.search(s2, tel).group())
099-898-2392
>>> print(re.search(s2, tel).group(0))  # 獲得的是整個匹配的物件
099-898-2392
>>> print(re.search(s2, tel).group(1))  # 第一個子組099
099
>>> print(re.search(s2, tel).group(2))  # 獲得的第二個子組898
898
>>> print(re.search(s2, tel).group(3))  # 獲得的第三個子組2392
2392
>>> print(re.search(s2, tel).groups())  # 獲得子組的一個列表
('099', '898', '2392')
>>>

方法三:  match,search, findall

  match: 在字串的開頭對寫的正則表示式進行匹配。 匹配成功,返回匹配物件,匹配失敗,返回None

  search:在整個字串中對寫的正則表示式進行匹配。 匹配成功,返回第一個被匹配的物件,匹配失敗,返回None

  findall: 在整個字串中對寫的正則表示式進行匹配。只要是匹配成功的就新增到列表中,最後返回一個列表

match:

>>> re.match('foo', 'food on the table').group()  # 從字串開頭開始匹配,匹配到了foo,所以group得到的是匹配到的foo
'foo'
>>> re.match('ood', 'food on the table')  # 從字串開頭開始匹配,發現並不是00d,所以沒有匹配到結果,返回一個空
>>>

search:

>>> re.match('foo', 'seafood')   # 字串開頭並不是foo,所以match沒有匹配到
>>> re.search('foo', 'seafood').group()   # search是在整個字串中匹配的,所以
'foo'
>>>

findall:

>>> re.match('foo', 'seafood, seafood')   # 字串開頭並不是foo,所以沒有匹配
>>> re.search('foo', 'seafood, seafood').group()  # 在字串匹配到第一個foo的時候就不再進行匹配了
'foo'
>>> re.findall('foo', 'seafood, seafood')  # 在字串給中查詢到所有匹配到的字串放在列表中
['foo', 'foo']
>>> 

方法四: finditer, findall

  finditer和findall作用其實是一樣的,不同之處在於finditer返回的是一個迭代器(更加節省記憶體),而findall返回的是一個列表。

  (1).在沒有分組的情況下,每一個被匹配的元素都會作為列表的元素

  (2).在分組的情況下,被匹配的元素會把子組放在一個元組中放在列表中(比較繞,直接上例子)

(1)在沒有分組的情況下

s = 'This and that.'
print(re.findall(r'Th\w+ and th\w+', s))   # 會把匹配到的資訊一個一個的放在列表中,此處只是匹配了一個
print(re.finditer(r'Th\w+ and th\w+', s).__next__())  # iter返回一個迭代器,可以通過__next__去獲得第一個物件,注意此處是類似於match獲得的物件
print(re.match('s', 's'))     # 為了和上面進行對比的
# 結果:
# ['This and that']
# <_sre.SRE_Match object; span=(0, 13), match='This and that'>
# <_sre.SRE_Match object; span=(0, 1), match='s'>

(2)有分組的情況

s = 'This and that.'
print(re.findall(r'(Th\w+) and (th\w+)', s))   # 有分組就會發現列表中其實放的並不是匹配到的值了,而是子組元組
print(re.finditer(r'(Th\w+) and (th\w+)', s).__next__())  #但是iter得到的還是匹配的物件,如果想得到子組可以通過group去獲得
print(re.match('s', 's'))     # 為了和上面進行對比的

# 結果:
# [('This', 'that')]
# <_sre.SRE_Match object; span=(0, 13), match='This and that'>
# <_sre.SRE_Match object; span=(0, 1), match='s'>

方法五: sub

  sub將搜尋匹配到的字串替換成另外一種字串。

# 引數一: 正則表示式
# 引數二: 要替換的字串
# 引數三: 源字串
# 將替換好的字串進行返回
s = re.sub('X', 'Mr. Smith', 'attn: X\n\nDear X, \n')
print(s)


#結果:
# attn: Mr. Smith
# 
# Dear Mr. Smith, 
# 引數一: 正則表示式
# 引數二: 要替換的字串
# 引數三: 源字串
# 將替換好的字串進行返回
s = re.sub('[ae]', 'X', 'abcdef')
print(s)

# 結果:
# XbcdXf

例子: 將美式的日期表示法轉換成其他國家的日期表示法

# 2/20/[1991|91]   月/日/年  美國
# 20/2/1991   日/月/年


# 首先先把美國的時間格式用正則表示式匹配到
m = '(\d{1,2})/(\d{1,2})/(\d{4}|\d{2})'
# 然後\N進行分組替換
s = re.sub(m, r'\2/\1/\3', '2/20/1991')
print(s)

方法六: split

  (1). 如果給定的模式不是特殊字元,那麼此方法和字串的split是一樣的

  (2). 可以通過設定一個max值來確定需要分割幾次

  python核心程式設計中提示我們: 能用字串的split分割的儘量不要使用影響效能的正則表示式進行分割。

print(re.split(':', 'str1:str2:str3'))

# 結果:
# ['str1', 'str2', 'str3']

 四. python3的re模組練習

 練習一: 擇一匹配多個字串

>>> # 擇一匹配
... bt = 'bat|bet|bit'
>>> print(re.match(bt, 'bt'))   # 因為此時的bt是擇一匹配,也就是隻能匹配bat或者bet或者bit字串
None
>>> print(re.match(bt, 'he bit me'))  # 雖然字串中有bit但是不是在開頭,所以匹配不到
None
>>> print(re.search(bt, 'he bit me').group())  # 這個search在整個字串中進行查詢,是可以匹配到的
bit
>>> 

練習二: 使用【.】來匹配除了換行符以外的任意字元

>>> anyend = '.end'
>>> print(re.match(anyend, 'bend').group())   # .匹配了b
bend
>>> print(re.match(anyend, 'end'))   # 任意字元但不是沒有,所以沒有匹配到
None
>>> print(re.match(anyend, '\nend'))  # 任意的非換行符,所以沒有匹配到
None
>>> print(re.search(anyend, 'the end').group())  # 用search去匹配空格一個end
 end
>>> print(re.match('3.14', '3014').group())   # . 沒有加轉義符代表的是匹配任意的非換行符,所以可以匹配到
3014 
>>> print(re.match('3\.14', '3.14').group())  # 這個.被轉義 了
3.14
>>>

練習三: 建立字符集的使用 【[ab][cd]】

>>> re.match('[c2][23][dp][o2]', 'c3po').group()  # 在正則表示式的每個方框中隨機挑一個進行組合
'c3po'
>>> re.match('[c2][23][dp][o2]', 'c2do').group()
'c2do'
>>> re.match('r2d2|c3po', 'c2do')   # 擇一匹配只能是r2d2或者是c3po和建立字符集還是有差別的
>>> re.match('r2d2|c3po', 'r2d2')
<_sre.SRE_Match object; span=(0, 4), match='r2d2'>
>>>

練習四: 重複匹配和分組

>>> # 郵件.com前面出現一個名稱
... mail1 = '\[email protected]\w+\.com'   # 可以匹配類似於[email protected]
>>> # 擴充套件一下,使得.com前面可以出現一個或者兩個名稱
... mail2 = '\[email protected](\w+\.)?\w+\.com'  # 可以匹配類似於[email protected]
>>>
>>> # 再擴充套件一下,使得.com前面可以出現任意次數的名稱
... mail3 = '\[email protected](\w+\.)*\w+\.com'
>>> re.match(mail1, '[email protected]').group()
'[email protected]'
>>> re.match(mail2, '[email protected]').group()
'[email protected]'
>>> re.match(mail1, '[email protected]')
>>> re.match(mail3, '[email protected]').group()
'[email protected]'
>>>

練習五:分組的使用

>>> re.match('(\w+)-(\d+)', 'abcde-123')   # 匹配字串
<_sre.SRE_Match object; span=(0, 9), match='abcde-123'>
>>> re.match('(\w+)-(\d+)', 'abcde-123').group()  # 匹配完成之後用group()的到匹配的結果
'abcde-123'
>>> re.match('(\w+)-(\d+)', 'abcde-123').group(1)  # 用索引1得到分組一,也就是第一個括號匹配的資料
'abcde'
>>> re.match('(\w+)-(\d+)', 'abcde-123').group(2)# 用索引2得到分組二,也就是第二個括號匹配的資料
'123'
>>>

練習六: 位置匹配在python中的使用

>>> re.search('^The', 'The end.').group()   # ^代表起始位置
'The'
>>> re.search('^The', 'end. The')     # 不作為起始地址
>>> re.search(r'\bThe', 'bite The Dog').group()   #The左邊是個邊界,直白講就是沒有英文字元
'The'
>>> re.search(r'\bThe', 'biteThe Dog')    # The左邊不是一個邊界,所以沒有匹配到
>>> re.search(r'\BThe', 'biteThe Dog').group()  # B代表的就是不是邊界的時候才會被匹配
'The'
>>>

五. 第一章課後練習題

前期的準備:

生成一個用來作為re練習的隨機字串的程式碼:

import random
from string import ascii_lowercase as lc
import sys
import time
tlds = ('com', 'edu', 'net', 'org', 'gov')
for i in range(random.randrange(5, 11)):
    dtint = random.randrange(sys.maxsize)
    dtstr = time.ctime(dtint)
    llen = random.randrange(4, 8)
    login = ''.join(random.choice(lc) for j in range(llen))
    dlen = random.randrange(llen, 13)
    dom = ''.join(random.choice(lc) for j in range(llen))
    msg = '%s::%[email protected]%s.%s::%d-%d-%d\n' %(
        dtstr,
        login,
        dom,
        random.choice(tlds),
        dtint,
        llen,
        dlen
    )
    with open('redata.txt', 'at', encoding='utf-8') as f:
        f.write(msg)
生成隨機字串

 知識點儲備一:找到時間戳中一週的第幾天

>>> # 示例一: 找到時間戳中一週的第幾天
... data = 'Wed Sep 16 11:17:21 1992::[email protected]::716613441-5-6'
>>> # 通過擇一匹配把每天的單詞縮寫放在一個括號內,然後^表示的是一個單詞的起始
... # 但是有的地方每天的單詞縮寫並不是這樣的,所以這樣子的正則表示式的適用性並不是很強
... pattr1 = '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'
>>> print(re.search(pattr1, data).group())
Wed
>>> print(re.search(pattr1, data).group(1))
Wed
>>>
>>>
>>> # 因此下面的正則表示式的意思是任意的字母數字字元三個,但是注意寫成這樣'^(\w){3}'是有問題的
... pattr2 = '^(\w{3})'
>>> print(re.search(pattr2, data).group())
Wed
>>> print(re.search(pattr2, data).group(1))
Wed
>>>
>>>
>>> # 注意寫成這樣是有問題的
... # 因為將{3}寫在括號外部代表的是分組只有一個字元那就是\w,因此在匹配的過程中分組的值是在不停的更新迭代的,最後變成三個字元中的最後一個
... # 啟發: 我們分組想要什麼樣的值,就把()放在哪裡。
... pattr3 = '^(\w){3}'
>>> print(re.search(pattr3, data).group())
Wed
>>> print(re.search(pattr3, data).group(1))
d
>>>

 知識點儲備二: 貪婪匹配

# 示例二:想得到由三個連字元分隔的整數
data = 'Wed Sep 16 11:17:21 1992::[email protected]::716613441-5-6'

# 通過這樣的方式我們可以匹配到,但是隻能稱之為搜尋,我們往往需要做的是匹配整個字串,然後通過分組的形式獲得我們需要的值
pattr1 = '\d+-\d+-\d+'
print(re.search(pattr1, data).group())

# .+ 任意字元出現至少一次,因此匹配到了三個連字元整數的前面,之後通過後面的匹配整數,這樣就實現了對整個字串的匹配
# 然後通過分組獲得我們需要的值
pattr2 = '.+(\d+-\d+-\d+)'
# 當我們要獲得分組的時候卻發現此時的值並不是我們想要的,這是因為貪婪匹配的原因
# .+ 或匹配符合它要求的所有字串,然後才會給後面的正則表示式進行匹配
print(re.search(pattr2, data).group(1))


# 我們可以通過?來結束貪婪匹配
pattr3 = '.+?(\d+-\d+-\d+)'
print(re.search(pattr3, data).group(1))


# 結果:
# 716613441-5-6
# 1-5-6
# 716613441-5-6

作業題:

import re
# 1-1 識別後續的字串:“bat”、“bit”、“but”、“hat”、“hit”或者“hut”。
print(re.search('[bh][aiu]t', 'hut').group())
# 1-2 匹配由單個空格分隔的任意單詞對,也就是姓和名。
print(re.search(r'\b[a-zA-Z]+\s[a-zA-Z]+\b', 'Tom Jerry Hello Bye House Good God').group())
# 1-3 匹配由單個逗號和單個空白符分隔的任何單詞和單個字母,如姓氏的首字母。
print(re.search(r'\w+,\s\w+', 'a, b').group())
# 1-4 匹配所有有效 Python 識別符號的集合。
print(re.search(r'^[a-zA-Z_]\w*', 'hello_world1_').group()) # 此處寫的是python的有效變數名的正則表示式
# 1-5 根據讀者當地的格式,匹配街道地址(使你的正則表示式足夠通用,來匹配任意數量的街道單詞,包括型別名稱)。
# 例如,美國街道地址使用如下格式: 1180 Bordeaux Drive。使你的正則表示式足夠靈活, 以支援多單詞的街道名稱,如 3120 De la Cruz Boulevard。
print(re.search(r'^\d+(\s\w+)*', '3120 De la Cruz Boulevard').group())
# 1-6 匹配以“www”起始且以“.com”結尾的簡單 Web 域名;例如, www://www. yahoo.com/。
# 選做題: 你的正則表示式也可以支援其他高階域名,如.edu、 .net 等(例如,http://www.foothill.edu)。
print(re.search('^w{3}.*\.(com|edu|net)', 'www.foothill.edu.com').group())
# 1-7 匹配所有能夠表示 Python 整數的字串集。python3中已經不再區分整形和長整形了
print(re.search(r'^0$|(^-?[1-9]\d*$)', '-2147483647').group())  # python中浮點數點是肯定要有的
# 1-9 匹配所有能夠表示 Python 浮點數的字串集。 .23和1.都是正確的?,三種情況,前面
print(re.match(r'(^\d+\.\d*$)|(^\d*\.\d+$)|(^\d+\.\d+$)', '09.23238').group())
# 1-10 匹配所有能夠表示 Python 複數的字串集。
# 1+2j  -j  +j  寫出來的也只能滿足一部分吧
print(re.search(r'(\d+\.?\d*[+-]?\d+\.?\d*j)|([+-]?\d+\.?\d*j)', '-2.2j').group())
# 1-11 匹配所有能夠表示有效電子郵件地址的集合(從一個寬鬆的正則表示式開始,然後嘗試使它儘可能嚴謹,不過要保持正確的功能
print(re.search('\[email protected]\w+\.\w+\.com', '[email protected]').group())
print(re.search('\[email protected](\w+\.)+\w+\.(com|edu|org)', '[email protected]').group())
# 1-12 匹配所有能夠表示有效的網站地址的集合(URL)(從一個寬鬆的正則表示式開始,然後嘗試使它儘可能嚴謹,不過要保持正確的功能)。
print(re.search('^(https?//:)?(w{3}\.)?.*\.\w{3}', 'https://www.lsejg.lwegfoothill.edu.com').group())
"""
1-13 type()。 內建函式 type()返回一個型別物件,如下所示,該物件將表示為一個Pythonic型別的字串。
>>> type(0)
<type 'int'>
>>> type(.34)
<type 'float'>
>>> type(dir)
<type 'builtin_function_or_method'>
建立一個能夠從字串中提取實際型別名稱的正則表示式。函式將對類似於<type'int' >的字串返回 int(其他型別也是如此,如 'float' 、 'builtin_function_or_method' 等)。
注意: 你所實現的值將存入類和一些內建型別的__name__屬性中。
"""
print(re.search(r"^<type\s*'([a-zA-Z_]+)'\s*>$", "<type 'builtin_function_or_method'>").group(1))
# 1-14 處理日期。1.2 節提供了來匹配單個或者兩個數字字串的正則表示式模式,來表示 1~9 的月份(0?[1-9])。建立一個正則表示式來表示標準日曆中剩餘三個月的數字。
print(re.search('1[0-2]', '10').group())
'''
1-15 處理信用卡號碼。 1.2 節還提供了一個能夠匹配信用卡(CC)號碼([0-9]{15,16})的正則表示式模式。然而,該模式不允許使用連字元來分割數字塊。
建立一個允許使用連字元的正則表示式,但是僅能用於正確的位置。例如, 15 位的信用卡號碼使用 4-6-5 的模式,表明 4 個數字-連字元-6 個數字-連字元-5 個數字; 16 位的
信用卡號碼使用 4-4-4-4 的模式。記住, 要對整個字串進行合適的分組。 
選做題:有一個判斷信用卡號碼是否有效的標準演算法。編寫一些程式碼,這些程式碼不但能夠識別具有正確格式的號碼, 而且能夠識別有效的信用卡號碼。
'''
print(re.search('([0-9]{4}-[0-9]{6}-[0-9]{5})|([0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4})', '0000-0923-0099-8899').groups())
1-15題附答案
# 1-17 判斷在 redata.tex 中一週的每一天出現的次數(換句話說,讀者也可以計算所選擇的年份中每個月中出現的次數)。
# 分析,因為我們只需要一週的每一天,因此我們只需要得到前面的時間戳就ok了,後面直接.*匹配
import re
file_path = 'redata.txt'
pattr = '^(\w{3}).*'
week = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
week_dict = {key: 0 for key in week}
print(week_dict)
with open(file_path, 'rt', encoding='utf-8') as f:
    obj_list = [re.search(pattr, each_line.rstrip('\n')) for each_line i