正則表達式是一個特殊的字符序列,用於判斷一個字符串是否與咱們所設定的字符序列是否匹配,也就是說檢查一個字符串是否與某種模式匹配。java
Python 自 1.5 版本起增長了re 模塊,它提供 Perl 風格的正則表達式模式。re 模塊使 Python 語言擁有所有的正則表達式功能。python
下面經過實例,一步一步來初步認識正則表達式。android
好比在一段字符串中尋找是否含有某個字符或某些字符,一般咱們使用內置函數來實現,以下:git
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程序員|ReadingWithU'
# 判斷是否有 「兩點水」 這個字符串,使用 PY 自帶函數
print('是否含有「兩點水」這個字符串:{0}'.format(a.index('兩點水') > -1))
print('是否含有「兩點水」這個字符串:{0}'.format('兩點水' in a))
複製代碼
輸出的結果以下:程序員
是否含有「兩點水」這個字符串:True
是否含有「兩點水」這個字符串:True
複製代碼
那麼,若是使用正則表達式呢?github
剛剛提到過,Python 給咱們提供了 re 模塊來實現正則表達式的全部功能,那麼咱們先使用其中的一個函數:正則表達式
re.findall(pattern, string[, flags])
複製代碼
該函數實現了在字符串中找到正則表達式所匹配的全部子串,並組成一個列表返回,具體操做以下:微信
import re
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程序員|ReadingWithU'
# 正則表達式
findall = re.findall('兩點水', a)
print(findall)
if len(findall) > 0:
print('a 含有「兩點水」這個字符串')
else:
print('a 不含有「兩點水」這個字符串')
複製代碼
輸出的結果:函數
['兩點水']
a 含有「兩點水」這個字符串
複製代碼
從輸出結果能夠看到,能夠實現和內置函數同樣的功能,但是在這裏也要強調一點,上面這個例子只是方便咱們理解正則表達式,這個正則表達式的寫法是毫無心義的。爲何這樣說呢?工具
由於用 Python 自帶函數就能解決的問題,咱們就不必使用正則表達式了,這樣作畫蛇添足。並且上面例子中的正則表達式設置成爲了一個常量,並非一個正則表達式的規則,正則表達式的靈魂在於規則,因此這樣作意義不大。
那麼正則表達式的規則怎麼寫呢?先不急,咱們一步一步來,先來一個簡單的,找出字符串中的全部小寫字母。首先咱們在 findall
函數中第一個參數寫正則表達式的規則,其中 [a-z]
就是匹配任何小寫字母,第二個參數只要填寫要匹配的字符串就好了。具體以下:
import re
# 設定一個常量
a = '兩點水|twowater|liangdianshui|草根程序員|ReadingWithU'
# 選擇 a 裏面的全部小寫英文字母
re_findall = re.findall('[a-z]', a)
print(re_findall)
複製代碼
輸出的結果:
['t', 'w', 'o', 'w', 'a', 't', 'e', 'r', 'l', 'i', 'a', 'n', 'g', 'd', 'i', 'a', 'n', 's', 'h', 'u', 'i', 'e', 'a', 'd', 'i', 'n', 'g', 'i', 't', 'h']
複製代碼
這樣咱們就拿到了字符串中的全部小寫字母了。
好了,經過上面的幾個實例咱們初步認識了 Python 的正則表達式,可能你就會問,正則表達式還有什麼規則,什麼字母表明什麼意思呢?
其實,這些都不急,在本章後面會給出對應的正則表達式規則列表,並且這些東西在網上隨便都能 Google 到。因此如今,咱們仍是進一步加深對正則表達式的理解,講一下正則表達式的字符集。
字符集是由一對方括號 「[]」 括起來的字符集合。使用字符集,能夠匹配多個字符中的一個。
舉個例子,好比你使用 C[ET]O
匹配到的是 CEO 或 CTO ,也就是說 [ET]
表明的是一個 E 或者一個 T 。像上面提到的 [a-z]
,就是全部小寫字母中的其中一個,這裏使用了連字符 「-」 定義一個連續字符的字符範圍。固然,像這種寫法,裏面能夠包含多個字符範圍的,好比:[0-9a-fA-F]
,匹配單個的十六進制數字,且不分大小寫。注意了,字符和範圍定義的前後順序對匹配的結果是沒有任何影響的。
其實說了那麼多,只是想證實,字符集一對方括號 「[]」 裏面的字符關係是或關係,下面看一個例子:
import re
a = 'uav,ubv,ucv,uwv,uzv,ucv,uov'
# 字符集
# 取 u 和 v 中間是 a 或 b 或 c 的字符
findall = re.findall('u[abc]v', a)
print(findall)
# 若是是連續的字母,數字可使用 - 來代替
l = re.findall('u[a-c]v', a)
print(l)
# 取 u 和 v 中間不是 a 或 b 或 c 的字符
re_findall = re.findall('u[^abc]v', a)
print(re_findall)
複製代碼
輸出的結果:
['uav', 'ubv', 'ucv', 'ucv']
['uav', 'ubv', 'ucv', 'ucv']
['uwv', 'uzv', 'uov']
複製代碼
在例子中,使用了取反字符集,也就是在左方括號 「[」 後面緊跟一個尖括號 「^」,就會對字符集取反。須要記住的一點是,取反字符集必需要匹配一個字符。好比:q[^u]
並不意味着:匹配一個 q,後面沒有 u 跟着。它意味着:匹配一個 q,後面跟着一個不是 u 的字符。具體能夠對比上面例子中輸出的結果來理解。
咱們都知道,正則表達式自己就定義了一些規則,好比 \d
,匹配全部數字字符,其實它是等價於 [0-9],下面也寫了個例子,經過字符集的形式解釋了這些特殊字符。
import re
a = 'uav_ubv_ucv_uwv_uzv_ucv_uov&123-456-789'
# 歸納字符集
# \d 至關於 [0-9] ,匹配全部數字字符
# \D 至關於 [^0-9] , 匹配全部非數字字符
findall1 = re.findall('\d', a)
findall2 = re.findall('[0-9]', a)
findall3 = re.findall('\D', a)
findall4 = re.findall('[^0-9]', a)
print(findall1)
print(findall2)
print(findall3)
print(findall4)
# \w 匹配包括下劃線的任何單詞字符,等價於 [A-Za-z0-9_]
findall5 = re.findall('\w', a)
findall6 = re.findall('[A-Za-z0-9_]', a)
print(findall5)
print(findall6)
複製代碼
輸出結果:
['1', '2', '3', '4', '5', '6', '7', '8', '9']
['1', '2', '3', '4', '5', '6', '7', '8', '9']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '&', '-', '-']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']
['u', 'a', 'v', '_', 'u', 'b', 'v', '_', 'u', 'c', 'v', '_', 'u', 'w', 'v', '_', 'u', 'z', 'v', '_', 'u', 'c', 'v', '_', 'u', 'o', 'v', '1', '2', '3', '4', '5', '6', '7', '8', '9']
複製代碼
來,繼續加深對正則表達式的理解,這部分理解一下數量詞,爲何要用數量詞,想一想都知道,若是你要匹配幾十上百的字符時,難道你要一個一個的寫,因此就出現了數量詞。
數量詞的詞法是:{min,max} 。min 和 max 都是非負整數。若是逗號有而 max 被忽略了,則 max 沒有限制。若是逗號和 max 都被忽略了,則重複 min 次。好比,\b[1-9][0-9]{3}\b
,匹配的是 1000 ~ 9999 之間的數字( 「\b」 表示單詞邊界),而 \b[1-9][0-9]{2,4}\b
,匹配的是一個在 100 ~ 99999 之間的數字。
下面看一個實例,匹配出字符串中 4 到 7 個字母的英文
import re
a = 'java*&39android##@@python'
# 數量詞
findall = re.findall('[a-z]{4,7}', a)
print(findall)
複製代碼
輸出結果:
['java', 'android', 'python']
複製代碼
注意,這裏有貪婪和非貪婪之分。那麼咱們先看下相關的概念:
貪婪模式:它的特性是一次性地讀入整個字符串,若是不匹配就吐掉最右邊的一個字符再匹配,直到找到匹配的字符串或字符串的長度爲 0 爲止。它的宗旨是讀儘量多的字符,因此當讀到第一個匹配時就馬上返回。
懶惰模式:它的特性是從字符串的左邊開始,試圖不讀入字符串中的字符進行匹配,失敗,則多讀一個字符,再匹配,如此循環,當找到一個匹配時會返回該匹配的字符串,而後再次進行匹配直到字符串結束。
上面例子中的就是貪婪的,若是要使用非貪婪,也就是懶惰模式,怎麼呢?
若是要使用非貪婪,則加一個 ?
,上面的例子修改以下:
import re
a = 'java*&39android##@@python'
# 貪婪與非貪婪
re_findall = re.findall('[a-z]{4,7}?', a)
print(re_findall)
複製代碼
輸出結果以下:
['java', 'andr', 'pyth']
複製代碼
從輸出的結果能夠看出,android 只打印除了 andr ,Python 只打印除了 pyth ,由於這裏使用的是懶惰模式。
固然,還有一些特殊字符也是能夠表示數量的,好比:
?
:告訴引擎匹配前導字符 0 次或 1 次
+
:告訴引擎匹配前導字符 1 次或屢次
*
:告訴引擎匹配前導字符 0 次或屢次
把這部分的知識點總結一下,就是下面這個表了:
貪 婪 | 惰 性 | 描 述 |
---|---|---|
? | ?? | 零次或一次出現,等價於{0,1} |
+ | +? | 一次或屢次出現 ,等價於{1,} |
* | *? | 零次或屢次出現 ,等價於{0,} |
{n} | {n}? | 剛好 n 次出現 |
{n,m} | {n,m}? | 至少 n 次枝多 m 次出現 |
{n,} | {n,}? | 至少 n 次出現 |
將上面幾個點,就用了很大的篇幅了,如今介紹一些邊界匹配符和組的概念。
通常的邊界匹配符有如下幾個:
語法 | 描述 |
---|---|
^ | 匹配字符串開頭(在有多行的狀況中匹配每行的開頭) |
$ | 匹配字符串的末尾(在有多行的狀況中匹配每行的末尾) |
\A | 僅匹配字符串開頭 |
\Z | 僅匹配字符串末尾 |
\b | 匹配 \w 和 \W 之間 |
\B | [^\b] |
分組,被括號括起來的表達式就是分組。分組表達式 (...)
其實就是把這部分字符做爲一個總體,固然,能夠有多分組的狀況,每遇到一個分組,編號就會加 1 ,並且分組後面也是能夠加數量詞的。
此處本應有例子,考慮到篇幅問題,就不貼了
實戰過程當中,咱們不少時候須要替換字符串中的字符,這時候就能夠用到 def sub(pattern, repl, string, count=0, flags=0)
函數了,re.sub 共有五個參數。其中三個必選參數:pattern, repl, string ; 兩個可選參數:count, flags .
具體參數意義以下:
參數 | 描述 |
---|---|
pattern | 表示正則中的模式字符串 |
repl | repl,就是replacement,被替換的字符串的意思 |
string | 即表示要被處理,要被替換的那個 string 字符串 |
count | 對於pattern中匹配到的結果,count能夠控制對前幾個group進行替換 |
flags | 正則表達式修飾符 |
具體使用能夠看下下面的這個實例,註釋都寫的很清楚的了,主要是注意一下,第二個參數是能夠傳遞一個函數的,這也是這個方法的強大之處,例如例子裏面的函數 convert
,對傳遞進來要替換的字符進行判斷,替換成不一樣的字符。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
a = 'Python*Android*Java-888'
# 把字符串中的 * 字符替換成 & 字符
sub1 = re.sub('\*', '&', a)
print(sub1)
# 把字符串中的第一個 * 字符替換成 & 字符
sub2 = re.sub('\*', '&', a, 1)
print(sub2)
# 把字符串中的 * 字符替換成 & 字符,把字符 - 換成 |
# 一、先定義一個函數
def convert(value):
group = value.group()
if (group == '*'):
return '&'
elif (group == '-'):
return '|'
# 第二個參數,要替換的字符能夠爲一個函數
sub3 = re.sub('[\*-]', convert, a)
print(sub3)
複製代碼
輸出的結果:
Python&Android&Java-888
Python&Android*Java-888
Python&Android&Java|888
複製代碼
re.match 函數
語法:
re.match(pattern, string, flags=0)
複製代碼
re.match 嘗試從字符串的起始位置匹配一個模式,若是不是起始位置匹配成功的話,match() 就返回 none。
re.search 函數
語法:
re.search(pattern, string, flags=0)
複製代碼
re.search 掃描整個字符串並返回第一個成功的匹配。
re.match 和 re.search 的參數,基本一致的,具體描述以下:
參數 | 描述 |
---|---|
pattern | 匹配的正則表達式 |
string | 要匹配的字符串 |
flags | 標誌位,用於控制正則表達式的匹配方式,如:是否區分大小寫 |
那麼它們之間有什麼區別呢?
re.match 只匹配字符串的開始,若是字符串開始不符合正則表達式,則匹配失敗,函數返回 None;而 re.search 匹配整個字符串,直到找到一個匹配。這就是它們之間的區別了。
re.match 和 re.search 在網上有不少詳細的介紹了,但是再我的的使用中,仍是喜歡使用 re.findall
看下下面的實例,能夠對比下 re.search 和 re.findall 的區別,還有多分組的使用。具體看下注釋,對比一下輸出的結果:
示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 提取圖片的地址
import re
a = '<img src="https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg">'
# 使用 re.search
search = re.search('<img src="(.*)">', a)
# group(0) 是一個完整的分組
print(search.group(0))
print(search.group(1))
# 使用 re.findall
findall = re.findall('<img src="(.*)">', a)
print(findall)
# 多個分組的使用(好比咱們須要提取 img 字段和圖片地址字段)
re_search = re.search('<(.*) src="(.*)">', a)
# 打印 img
print(re_search.group(1))
# 打印圖片地址
print(re_search.group(2))
# 打印 img 和圖片地址,以元祖的形式
print(re_search.group(1, 2))
# 或者使用 groups
print(re_search.groups())
複製代碼
輸出的結果:
<img src="https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg">
https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg
['https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg']
img
https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg
('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')
('img', 'https://s-media-cache-ak0.pinimg.com/originals/a8/c4/9e/a8c49ef606e0e1f3ee39a7b219b5c05e.jpg')
複製代碼
最後,正則表達式是很是厲害的工具,一般能夠用來解決字符串內置函數沒法解決的問題,並且正則表達式大部分語言都是有的。python 的用途不少,但在爬蟲和數據分析這連個模塊中都是離不開正則表達式的。因此正則表達式對於學習 Python 來講,真的很重要。最後,附送一些經常使用的正則表達式和正則表達式和 Python 支持的正則表達式元字符和語法文檔。
github:https://github.com/TwoWater/Python/blob/master/python14/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md
歡迎你們 start ,https://github.com/TwoWater/Python 一下,這是草根學 Python 系列博客的庫。也能夠關注個人微信公衆號: