1. 程式人生 > Python進階應用教學 >26 Python 中使用正則表示式

26 Python 中使用正則表示式

1. 正則表示式

1.1 簡介

正則表示式 (regular expression) 描述了一種字串匹配的模式 (pattern),例如:

  • 模式 ab+c
    • 可以匹配 abc、abbc、abbbc
      • 代表前面的字元出現 1 次或者多次
  • 模式 ab*c
    • 可以匹配 ac、abc、abbc
    • ? 代表前面的字元出現 0 次或者多次
  • 模式 ab?c
    • 可以匹配 ac、abc
    • ? 代表前面的字元出現 0 次或者 1 次

它的用途包括:

  • 檢查一個串是否含有某種子串
  • 將匹配的子串替換
  • 從某個串中取出符合某個條件的子串

1.2 普通字元

正則表示式是由普通字元(例如字元 a 到 z)以及特殊字元(稱為"元字元")組成的文字模式。模式描述在搜尋文字時要匹配的一個或多個字串。正則表示式作為一個模板,將某個字元模式與所搜尋的字串進行匹配。

普通字元包括沒有顯式指定為元字元的所有可列印和不可列印字元。這包括所有大寫和小寫字母、所有數字、所有標點符號和一些其他符號。

1.3 特殊字元

特殊字元是一些有特殊含義的字元,例如的 ab*c 中的 *,* 之前的字元是 b,* 表示匹配 0 個或者多個 字元 b。下表列出了正則表示式中的特殊字元:

特殊字元 描述
\t 製表符
\f 換頁符
\n 換行符
\r 回車符
\s 匹配任意空白字元,等價於 [\t\n\r\f]
\S 匹配任意非空字元
\d 匹配任意數字,等價於 [0-9]
\D 匹配任意非數字
^ 匹配字串的開頭
$ 匹配字串的末尾
. 匹配任意字元
\b 匹配一個單詞邊界,在單詞的開頭或者末尾匹配xcd ef’
\B 匹配非單詞邊界
[…] 用來表示一組字元
[^…] 不在[]中的字元
re* 匹配 0 個或多個正則表示式
re+ 匹配 1 個或多個正則表示式
re? 匹配 0 個或 1 個正則表示式
re{n} 匹配 n 個正則表示式
re{n,m} 匹配 n 到 m 個正則表示式
a | b 匹配 a或 b
(re) 對正則表示式分組並記住匹配的文字

2. 模組 re

2.1 簡介

Python 提供了 re 模組,提供正則表示式的模式匹配功能。在 re 模組中定義瞭如下常用函式:

函式 功能
re.match(pattern, string, flags) 從字串 string 的起始位置,查詢符合模式 pattern 的子串
re.search(pattern, string, flags) 從字串 string 的任意位置,查詢符合模式 pattern 的子串
re.split(pattern, string) 根據分隔符 pattern 將字串 string 分割
re.sub(pattern, replace, string) 將字串中匹配模式 patter 的子串替換字串 replace

2.2 正則表示式修飾符

正則表示式可以包含一些可選修飾符來控制匹配的模式。修飾符被指定為一個可選的標誌,多個標誌可以通過按位 OR(|) 它們來指定,如 re.I | re.M 被設定成 I 和 M 標誌。下表列舉了常用的正則表示式修飾符:

修飾符 描述
re.I 使匹配對大小寫不敏感
re.M 多行匹配,影響 ^ 和 $

2.3 re.MatchObject

re.MatchObject 表示模式匹配的結果,該物件包含 3 個成員方法:

  • start() 返回匹配開始的位置
  • end() 返回匹配結束的位置
  • span() 返回一個元組包含匹配 (開始,結束) 的位置

2.4 re.RegexObject

re.RegexObject 表示正則表示物件,該物件包含 2 個成員方法:

  • match(string) | 從字串 string 的起始位置,查詢符合模式 pattern 的子串
  • serach(string) | 從字串 string 的任意位置,查詢符合模式 pattern 的子串

3. 在字串查詢與模式匹配的字串

3.1 從字串的起始位置進行匹配

函式 re.match(pattern, string, flags = 0) 用於在字串查詢與模式匹配的字串:

  • 從字串 string 的起始位置,查詢符合模式 pattern 的子串
  • 如果匹配成功,則返回一個 re.MatchObject 物件
  • 如果匹配失敗,則返回 None
  • 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等

函式的使用示例如下:

>>> import re
>>> matchObject = re.match('w+', 'www.imooc.com')
>>> matchObject.group()
'www'
>>> matchObject.span()
(0, 3)
  • 在第 1 行,匯入模組 re
  • 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘w+’
    • 該模式匹配連續的小寫字元 W
    • 如果找到模式匹配的子字串,則返回一個匹配物件 matchObject
  • 在第 3 行,匹配物件 matchObject.group() 方法返回匹配的字串
  • 在第 5 行,匹配物件 matchObject.span() 方法返回一個元組
    • 元組的第 0 項,匹配的字串在原始字串中的起始位置
    • 元組的第 1 項,匹配的字串在原始字串中的結束位置
>>> import re
>>> matchObject = re.match('W+', 'www.imooc.com')
>>> matchObject is None
True
  • 在第 1 行,匯入模組 re
  • 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘W+’
    • 該模式匹配連續的大寫字元 W
    • 如果找不到模式匹配的子字串,則返回一個 None
>>> import re
>>> matchObject = re.match('o+', 'www.imooc.com')
>>> matchObject is None
True
  • 在第 1 行,匯入模組 re
  • 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘o+’
    • 該模式匹配連續的小寫字元 o
    • 如果找不到模式匹配的子字串,則返回一個 None
  • 在第 4 行,顯示匹配結果是 None
    • 儘管字元 string 的中間含有字串 oo
    • 函式 re.match 從字串 string 的開始位置進行匹配
    • 因此找不到匹配

3.2 從字串的任意位置進行匹配

函式 re.search(pattern, string, flags = 0) 用於在字串查詢與模式匹配的字串:

  • 從字串 string 的任意位置,查詢符合模式 pattern 的子串
  • 如果匹配成功,則返回一個 re.MatchObject 物件
  • 如果匹配失敗,則返回 None
  • 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等
>>> import re
>>> matchObject = re.search('o+', 'www.imooc.com')
>>> matchObject.group()
'oo'
>>> matchObject.span()
(6, 8)
  • 在第 1 行,匯入模組 re
  • 在第 2 行,在字串 ‘www.imooc.com’ 中查詢模式 ‘o+’
    • 該模式匹配連續的小寫字元 o
    • 如果找到模式匹配的子字串,則返回一個匹配物件 matchObject
  • 在第 3 行,匹配物件 matchObject.group() 方法返回匹配的字串
  • 在第 5 行,匹配物件 matchObject.span() 方法返回一個元組
    • 元組的第 0 項,匹配的字串在原始字串中的起始位置
    • 元組的第 1 項,匹配的字串在原始字串中的結束位置

3.3 在字串的首部進行匹配

>>> import re
>>> re.search('^a', 'abc')
<_sre.SRE_Match object; span=(0, 1), match='a'>
>>> re.search('^a', 'xabc')
>>>
  • 在第 2 行,^a 表示從字串 ‘abc’ 的首部進行匹配
    • 在第 3 行,顯示匹配結果不為 None
  • 在第 4 行,^a 表示從字串 ‘xabc’ 的首部進行匹配
    • 在第 5 行,顯示匹配結果為 None

3.4 在字串的尾部進行匹配

>>> import re
>>> re.search('c$', 'abc')
<_sre.SRE_Match object; span=(2, 3), match='c'>
>>> re.search('c$', 'abcx')
>>>
  • 在第 2 行,c$ 表示從字串 ‘abc’ 的尾部進行匹配
    • 在第 3 行,顯示匹配結果不為 None
  • 在第 4 行,c$ 表示從字串 ‘xabc’ 的尾部進行匹配
    • 在第 5 行,顯示匹配結果為 None

3.5 匹配一串數字

>>> import re
>>> re.search('\d+', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{3}', 'abc123xyz')
<_sre.SRE_Match object; span=(3, 6), match='123'>
>>> re.search('\d{4}', 'abc123xyz')
>>>
  • 在第 2 行,\d+ 表示匹配 1 個或者多個數字
    • 在第 3 行,顯示匹配結果不為 None
  • 在第 4 行,\d{3} 表示匹配 3 個數字
    • 在第 5 行,顯示匹配結果不為 None
  • 在第 6 行,\d+ 表示匹配 4 個數字
    • 在第 7 行,顯示匹配結果為 None

3.6 判斷是否是合法的變數名

Python 的變數命名規則如下:

  • 首個字元必須是字母或者字元 _
  • 其餘的字元可以是字元、數字或者字元 _

下面的例子使用正則表示式判斷字串是否是一個合法的變數名稱:

import re

def isPythonId(id):
    pattern = '^[a-zA-Z_][a-zA-Z0-9_]*$'
    matchObject = re.search(pattern, id)
    if matchObject is None:
        print('%s is not Id' % id)
    else:
        print('%s is Id' % id)

isPythonId('abc') 
isPythonId('Abc_123') 
isPythonId('123') 
  • 在第 3 行,定義了函式 isPythonId(id),判斷輸入字串 id 是否是一個合法的 Python 變數名
  • 在第 4 行,模式 pattern 定義了一個合法的 Python 變數名的模式,該模式由 4 個部分構成
模式 功能
^ 匹配字串頭部,即被匹配的字串從原始字串的頭部開始
[a-zA-Z_] 匹配小寫字元、大寫字元和字元 _
[a-zA-Z0-9_] 匹配小寫字元、大寫字元、數字和字元 _
* 將 * 之前的字元重複 0 次或者多次
$ 匹配字串尾部,即被匹配的字串以原始字串的尾部結尾

程式執行輸出結果如下:

abc is Id
Abc_123 is Id
123 is not Id

4. 將字串分割成多個部分

函式 re.split(pattern, string) 根據分隔符 pattern 將字串 string 分割

  • 返回一個列表,該列表記錄了分割的字串
  • 引數 pattern,描述了分隔符的模式
  • 引數 string,是被分割的字串
>>> import re
>>> re.split('[ :]', 'www imooc:com')
['www', 'imooc', 'com']
>>> re.split(' +', 'www imooc  com')
['www', 'imooc', 'com']

5. 在字串替換與模式匹配的字串

5.1 替換字串

函式 re.sub(pattern, replace, string, count=0, flags=0) 用於替換字串:

  • 在字串 string 中查詢與模式 pattern 匹配的子串,將其替換為字串 replace
  • 引數 replace,是被替換的字串,也可為一個函式
  • 引數 count,模式匹配後替換的最大次數,預設 0 表示替換所有的匹配
  • 引數 flags,用於控制正則表示式的匹配方式,如:是否區分大小寫,多行匹配等
import re

line = 'number = 123 # this is comment'
result = re.sub('\d+', 'NUMBER', line)
print(result)
result = re.sub('#.*$', '', line)
print(result)
  • 在第 4 行,搜尋字串 line,將與模式 ‘\d+’ 匹配的字串替換為 ‘NUMBER’
    • 模式 ‘\d+’ 匹配多個連續的數字
  • 在第 6 行,搜尋字串 line,將與模式 ‘#.*$’ 匹配的字串替換為 ‘’
    • 替換為空字串,即刪除匹配的字串
    • 模式 ‘#.*$’ 匹配從字元 # 開始到到結尾的字串,即行的註釋

程式輸出結果:

number = NUMBER # this is comment
number = 123
  • 在第 1 行,將數字 123 替換為 NUMBER
  • 在第 1 行,將以 # 開始的註釋刪除

5.2 使用函式替換字串

引數 replace 用於替換匹配的字串,它可以是一個函式。下面的例子將匹配的數字乘以 2:

import re

def replace(matchedObject):
    text = matchedObject.group()
    number = int(text)
    return str(number * 2)

line = 'number = 123'
result = re.sub('\d+', replace, line)
print(result)
  • 在第 8 行,定義了原始字串 line
  • 在第 9 行,使用 re.sub 搜尋符合模式 ‘\d+’ 的字串,使用函式 replace 進行替換
    • re.sub 找到符合模式 ‘\d+’ 的字串時,將匹配結果傳遞給 replace
    • 函式 replace 根據匹配結果,返回一個字串
    • re.sub 將符合模式的字串替換為函式 replace 的返回結果
  • 在第 3 行,定義了函式 replace
    • 在第 4 行,matchedObject.group() 返回匹配模式的字串
    • 在第 5 行,將匹配的字串轉換為整數
    • 在第 6 行,將整數乘以 2 後轉換為字串,並返回

程式輸出結果如下:

number = 246

6. 分組與捕獲

6.1 簡介

正則表示式中的分組又稱為子表示式,就是把一個正則表示式的全部或部分當做一個整體進行處理,分成一個或多個組。其中分組是使用 () 表示的。進行分組之後 ()裡面的內容就會被當成一個整體來處理。

把正則表示式中子表示式匹配的內容,儲存到記憶體中以數字編號或顯式命名的組裡,方便後面引用,被稱為捕獲。

6.2 分析 URL

import re

def parseUrl(url):
    pattern = '(.*)://(.*)/(.*)'
    matchObject = re.search(pattern, url)
    
    all = matchObject.group(0)
    protocol = matchObject.group(1)
    host = matchObject.group(2)
    path = matchObject.group(3)

    print('group(0) =', all)
    print('group(1) =', protocol)
    print('group(2) =', host)
    print('group(3) =', path)
    print()

parseUrl('https://www.imooc.com/wiki')
parseUrl('http://www.imooc.com/courses')
  • 在第 3 行,函式 parseUrl(url) 分析 URL 的組成部分
    • URL 由 3 部分構成:協議、主機名、路徑名
  • 在第 4 行,定義了匹配 URL 的模式 ‘(.)://(.)/(.*)’
    • 第 1 個 (.*) 匹配協議
    • 第 2 個 (.*) 匹配主機名
    • 第 3 個 (.*) 匹配路徑名
  • 匹配物件 matchObject 的 group(index) 方法返回指定分組號的分組
    • group(0) 為匹配整個表示式的字串
    • group(1) 為匹配的協議
    • group(2) 為匹配的主機名
    • group(3) 為匹配的路徑名

程式執行輸出:

group(0) = https://www.imooc.com/wiki
group(1) = https
group(2) = www.imooc.com
group(3) = wiki

group(0) = http://www.imooc.com/courses
group(1) = http
group(2) = www.imooc.com
group(3) = courses