草根學Python(十四) 一步一步瞭解正則表達式

目錄

草根學Python(十四) 一步一步瞭解正則表達式

初識 Python 正則表達式

正則表達式是一個特殊的字符序列,用於判斷一個字符串是否與咱們所設定的字符序列是否匹配,也就是說檢查一個字符串是否與某種模式匹配。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 ,並且分組後面也是能夠加數量詞的。

此處本應有例子,考慮到篇幅問題,就不貼了

re.sub

實戰過程當中,咱們不少時候須要替換字符串中的字符,這時候就能夠用到 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.search

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 系列博客的庫。也能夠關注個人微信公衆號:

https://user-gold-cdn.xitu.io/2017/12/26/1608e8e73726376a?w=1057&h=705&f=gif&s=1285550
相關文章
相關標籤/搜索