Python程式設計

Lesson 13: 正規表達式(Regular Expression)

字串基本功能

字串(str)資料型態

字串: 2個「單引號」或「雙引號」之間的字元符號資料

x1 = "這是字串資料型態的範例"
x2 = '使用單引號的字串'
x3 = '123'            # 雖是數字, 但用單引號框起來,故也是字串
x4 = "This is Mary's house." #字串包含了單引號, 故用雙引號框起來
x5 = '小明說:"這是我的書。"'    # 字串如包含雙引號, 則用單引號框起來

字串中「單引號」或「雙引號」的處理方式

加號'+'可用來「串接」兩個以上的字串

x1 = "字串1"
x2 = '字串2'
x3 = x1 + x2  # 串接, 存入另一個變數

print(x3)
print(x1 + x2) # 也可直接執行「串接」運算

x4 = '詞彙'
x5 = '頻率'
x6 = x4 + "," + x5    # 多個字串串接
print(x6)

字串超過1行:使用3個「單引號」或「雙引號」框起來

字串太長,超過一行以上怎麼辦?

使用3個「單引號」或「雙引號」框起來

# 字串內容太長,超過一行
x1 = """清晨進入一個曲折像蛇般的峽灣中,兩邊都是陸地,
上午8點見到了英國國旗在一個小山的山頂上揚起。
很快的,船之就進入到一個較為寬廣的水域,
四周被小山圍繞著。同時在兩邊高崗上,
層層羅列著房舍,終於到達香港。
回到艙房中,感謝上帝的照顧以及美善。
登岸,遇到艾德博士,他問我「你是馬偕嗎?」""" 

逸出字元:以「反斜線」'\'開頭的特殊字元

特殊字元需加上「反斜線」才能正常使用

str1 = '千山鳥飛絕 萬徑人蹤滅'  # 同一行
str2 = '千山鳥飛絕\n萬徑人蹤滅' # 加上換行字元\n
str3 = '千山鳥飛絕\t萬徑人蹤滅' # 加上Tab字元\t

str4 = 'This is John\'s pen.' # 單引號,雙引號的「逸出字元」的寫法

字串強制轉換函式:str()

str() 可將數值資料轉換為字串

score = int(input('輸入分數:'))
str1 = '你輸入的分數是' + score      # 錯誤:字串, 整數無法相加

str2 = '你輸入的分數是' + str(score) # 正確:字串相加
print(str2)

變數名稱請勿命名為str, 會將str()函式名稱遮住

words = ['學堂', '醫館', '講道']
counts = [123, 456, 145]

for i in range(len(words)):
    str1 = words[i] + ',' + str(counts[i])  # 3個字串串接
    print(str1)

字串的索引值

字串也有個索引值,可取得字串內個別字元

str1 = '千山鳥飛絕'
print(str1[3])   # 印出'飛'

for i in range(len(str1)):
    print(str1[i])         # 逐行印出str1內容

字串索引值: 以方括弧指定, 由0算起

str2 = '千山鳥飛絕 萬徑人蹤滅'
print('字串第1~3個字元', str2[0:3]) # 索引值0開始,2結束
print('字串第2~4個字元', str2[1:4]) # 索引值1開始,3結束
print('字串第2個字元到最後', str2[1:])  # 索引值1開始,到最後結束
print('字串第3,5,7字元', str2[2:7:2])  # 索引值2開始,6結束,每隔2取值

指定索引值範圍:取得部份字串內容

[開始值:結束值:間隔]
poem = '''
首先,它創造了峻潔清冷的藝術境界。單就詩的字面來看,
“孤舟蓑笠翁”一句似乎是作者描繪的重心,占據了畫面的主體地位。
這位漁翁身披蓑笠獨自坐在小舟上垂綸長釣。
“孤”與“獨”二字已經顯示出他的遠離塵世,甚至揭示出他清高脫俗、
兀傲不群的個性特征。作者所要表現的主題于此已然透出,
但是作者還嫌意興不足,又為漁翁精心創造了一個廣袤無垠、
萬籟俱寂的藝術背景:遠處峰巒聳立,萬徑縱橫,然而山無鳥飛,徑無人蹤。
往日沸騰喧鬧,處處生機盎然的自然界因何這般死寂呢?一場大雪紛紛揚揚。
'''
plist = []
index = poem.find('。') # 尋找下一個句點的位置
while index != -1:      # 有找到句點
    print(index)
    plist.append(poem[0:index])  # 儲存斷句:0開始,index值結束(不含句點)
    poem = poem[index+1:]    # 更新poem: 只留下句點之後剩餘的文字
    index = poem.find('。')  # 尋找下一個句點的位置
print(plist)

範例:利用索引值斷句

找出這段文字共有幾個句子

str2 = '千山鳥飛絕 萬徑人蹤滅'
for i in range(len(str2)):
    print('第%d個字元是%c' % (i+1, str2[i]))

len(): 字串長度

取得字串長度的函式: len()

第1個字元是千
第2個字元是山
第3個字元是鳥
第4個字元是飛
第5個字元是絕
第6個字元是
第7個字元是萬
第8個字元是徑
第9個字元是人
第10個字元是蹤
第11個字元是滅

執行結果

poem = '''
首先,它創造了峻潔清冷的藝術境界。單就詩的字面來看,
“孤舟蓑笠翁”一句似乎是作者描繪的重心,占據了畫面的主體地位。
這位漁翁身披蓑笠獨自坐在小舟上垂綸長釣。
“孤”與“獨”二字已經顯示出他的遠離塵世,甚至揭示出他清高脫俗、
兀傲不群的個性特征。作者所要表現的主題于此已然透出,
但是作者還嫌意興不足,又為漁翁精心創造了一個廣袤無垠、
萬籟俱寂的藝術背景:遠處峰巒聳立,萬徑縱橫,然而山無鳥飛,徑無人蹤。
往日沸騰喧鬧,處處生機盎然的自然界因何這般死寂呢?一場大雪紛紛揚揚。
'''
plist = poem.split('。')
print(plist)

找出這段文字共有幾個句子

拆分字串的函式: split()

['\n首先,它創造了峻潔清冷的藝術境界', '單就詩的字面來看,\n“孤舟蓑笠翁”一句似乎是作者描繪的重心,占據了畫面的主體地位', '\n這位漁翁身披蓑笠獨自坐在小舟上垂綸長釣', '\n“孤”與“獨”二字已經顯示出他的遠離塵世,甚至揭示
出他清高脫俗、\n兀傲不群的個性特征', '作者所要表現的主題于此已然透出,\n但是作者還嫌意興不足,又為漁翁精心創造了一個廣袤無垠、\n萬籟俱寂的藝術背景:遠處峰巒聳立,萬徑縱橫,然而山無鳥飛,徑無人蹤', '\n往日沸騰喧鬧,處處生
機盎然的自然界因何這般死寂呢?一場大雪紛紛揚揚', '\n']

執行結果

str2 = '千山鳥飛絕 萬徑人蹤滅'

word = input('輸入要查詢的字串:')
if word in str2:
    print('輸入的文字在字串中')
else:
    print('輸入的文字不在字串中')

以in運算子查詢字串內容

輸入要查詢的字串:飛
輸入的文字在字串中

輸入要查詢的字串:海
輸入的文字不在字串中

執行結果

正規表達式

Regular Expression

功能:模式比對與搜尋

def taiwanPhoneNum(string):
    """檢查是否有含手機聯絡資訊的台灣手機號碼格式"""
    if len(string) != 12:       # 如果長度不是12
        return False            # 傳回非手機號碼格式
    for i in range(0, 4):       # 如果前4個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格式        
    if string[4] != '-':        # 如果不是'-'字元
        return False            # 傳回非手機號碼格式
 
    for i in range(5, 8):       # 如果中間3個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格
    if string[8] != '-':        # 如果不是'-'字元
        return False            # 傳回非手機號碼格式
    for i in range(9, 12):      # 如果最後3個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格
    return True                 # 通過以上測試

print("a123456789: 是台灣手機號碼", taiwanPhoneNum('a123456789'))
print("0932-999-199:    是台灣手機號碼", taiwanPhoneNum('0932-999-199'))

如何比對手機號碼格式

def taiwanPhoneNum(string):
    if len(string) != 12:       # 如果長度不是12
        return False            # 傳回非手機號碼格式
    for i in range(0, 4):       # 如果前4個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格式        
    if string[4] != '-':        # 如果不是'-'字元
        return False            # 傳回非手機號碼格式
 
    for i in range(5, 8):       # 如果中間3個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格
    if string[8] != '-':        # 如果不是'-'字元
        return False            # 傳回非手機號碼格式
    for i in range(9, 12):      # 如果最後3個字出現非數字字元
        if string[i].isdecimal() == False:
            return False        # 傳回非手機號碼格
    return True                 # 通過以上測試
def parseString(string):
    """解析字串是否含有電話號碼"""
    notFoundSignal = True       # 註記沒有找到電話號碼為True
    for i in range(len(string)):  # 用迴圈逐步抽取12個字元做測試
        msg = string[i:i+12]
        if taiwanPhoneNum(msg):
            print("電話號碼是: %s" % msg)
            notFoundSignal = False        
    if notFoundSignal:          # 如果沒有找到電話號碼則列印
        print("%s 字串不含電話號碼" % string)

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '請明天13:00請到牛津藝術中心參加策展茶會'
msg3 = '請明天13:00請到牛津藝術中心參加策展茶會, 可用0933-080-080聯絡我'
parseString(msg1)
parseString(msg2)
parseString(msg3)

如何從訊息中擷取出手機號碼?

正規表達式是一種「文字模式」的表達方法

xxxx-xxx-xxx

其中,'\d' 表示0-9的數字字元

'\d\d\d\d-\d\d\d-\d\d\d'

以手機號碼為例:

格式表達方式

'\\d\\d\\d\\d-\\d\\d\\d-\\d\\d\\d'

Python字串中的'\'必須用「逸出字元」'\\'

r'\d\d\d\d-\d\d\d-\d\d\d'

或是在字串前加r:防止逸出字元被轉譯

正規表達式位於re模組,使用時要先建立Regex物件

❶使用re.compile()建立Regex物件

import re
...
手機規則 = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
...

❷呼叫Regex物件的search()搜尋想要比對的對象

import re

字串 = '請明天13:00請到牛津藝術中心參加策展茶會, 可用0933-080-080聯絡我'
手機規則 = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
比對結果 = 手機規則.search(字串)
if (比對結果 == None):
    print('找不到')
else:
    print(比對結果.group())
比對結果 = 手機規則.search(字串)

回傳None 或 MatchObject物件

import re

def parseString(string):
    """解析字串是否含有電話號碼"""
    手機規則 = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
    比對結果 = 手機規則.search(string)
    if 比對結果 != None:        # 檢查比對結果
        print("電話號碼是: %s" % 比對結果.group())
    else:
        print("%s 字串不含電話號碼" % string)

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '請明天13:00請到牛津藝術中心參加策展茶會'
msg3 = '請明天13:00請到牛津藝術中心參加策展茶會, 可用0933-080-080聯絡我'
parseString(msg1)
parseString(msg2)
parseString(msg3)

以正規表達式改寫手機號碼比對的範例

import re

def parseAllString(string):
    """解析字串中所有的電話號碼"""
    手機規則 = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
    比對結果串列 = 手機規則.findall(string)
    return 比對結果串列

msg1 = 'Please call my secretary using 0930-919-919 or 0952-001-001'
msg2 = '請明天13:00請到牛津藝術中心參加策展茶會'
msg3 = '請明天13:00請到牛津藝術中心參加策展茶會, 可用0933-080-080聯絡我'
print(parseAllString(msg1))
print(parseAllString(msg2))
print(parseAllString(msg3))

Regex物件的比對方法: search()與findall()

search(): 第一個比對相符的位置

findall(): 所有比對相符的字串串列

re.compile(比對規則)如何處理重複出現的字元?

import re
...
手機規則 = re.compile(r'\d\d\d\d-\d\d\d-\d\d\d')
...

規則:4個\d-3個\d-3個\d

\d重複出現,可用\d{重複次數}取代

import re
...
手機規則 = re.compile(r'\d{4}-\d{3}-\d{3}')
...

更多比對模式範例

xx-xxxxxxxx

'\d\d-\d\d\d\d\d\d\d\d'

以市話為例:

格式表達方式

02-26212121

r'\d{2}-\d{8}'

import re
...
市話規則 = re.compile(r'\d{2}-\d{8}')
...
import re

str1 = '台北校區 地址: 25103 新北市淡水區真理街32號 電話:02-26212121'
市話規則 = re.compile(r'\d{2}-\d{8}')
result = 市話規則.search(str1)
if (result != None):
    print('找到了 %s' % result.group())
else:
    print('找不到市話號碼')

更多比對模式範例: 市話

但,如果想要知道比對出的電話「區碼」,例如台北02, 桃園03,可能嗎?

import re

msg = '台北校區 地址: 25103 新北市淡水區真理街32號 電話:02-26212121'
pattern = r'(\d{2})-(\d{8})'  # \d{2}一組,,\d{8}一組
規則 = re.compile(pattern)
phoneNum = 規則.search(msg)           # 傳回搜尋結果

print("完整號碼是: %s" % phoneNum.group())   # 顯示完整號碼
print("完整號碼是: %s" % phoneNum.group(0))  # 顯示完整號碼
print("區域號碼是: %s" % phoneNum.group(1))  # 顯示區域號碼
print("電話號碼是: %s" % phoneNum.group(2))  # 顯示電話號碼

表達式可用小括弧分組,搭配.group()取得分組內容

r'\d{2}-\d{8}'

r'(\d{2})-(\d{8})'

前2個數字第一組,'-'號後8個數字第二組

更多比對模式範例: 市話的區碼是在小括弧內?

(xx)-xxxxxxxx

'\(\d\d\)-\d\d\d\d\d\d\d\d'

以市話為例:

用逸出字元表示括弧符號

(02)-26212121

r'\(\d{2}\)-\d{8}'

import re
...
市話規則 = re.compile(r'\(\d{2}\)-\d{8}'')
...

問號?、星號*、加號+ 在正規表達式裡的意義?

import re
# 測試1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)?son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
# 測試2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)?son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
import re
...
pattern = 'John((na)?son)'  # 問號前的分組可有可無
規則 = re.compile(pattern)
...

問號? :出現0次或1次(可有可無)

問號?、星號*、加號+ 在正規表達式裡的意義?

# ch16_16.py
import re

# 測試1
msg = 'Please call my secretary using 02-26669999'
pattern = r'(\d\d-)?(\d{8})'                 # 增加?號
規則 = re.compile(pattern)
phoneNum = 規則.search(msg)           # 傳回搜尋結果
print("完整號碼是: %s" % phoneNum.group())   # 顯示完整號碼

# 測試2
msg = 'Please call my secretary using 26669999'
pattern = r'(\d\d-)?(\d{8})'                 # 增加?號
規則 = re.compile(pattern)
phoneNum = 規則.search(msg)           # 傳回搜尋結果
print("完整號碼是: %s" % phoneNum.group())   # 顯示完整號碼

問號? 的範例:省略區域號碼的電話

問號?、星號*、加號+ 在正規表達式裡的意義?

import re
# 測試1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)*son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
# 測試2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)*son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
# 測試3
msg = 'Johnnanason will attend my party tonight.'
pattern = 'John((na)*son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())

星號* :出現0次以上(可重複)

問號?、星號*、加號+ 在正規表達式裡的意義?

import re
# 測試1
msg = 'Johnson will attend my party tonight.'
pattern = 'John((na)+son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
# 測試2
msg = 'Johnnason will attend my party tonight.'
pattern = 'John((na)+son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())
# 測試3
msg = 'Johnnanason will attend my party tonight.'
pattern = 'John((na)+son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
print(txt.group())

星號+ :出現1次以上(可重複)

re模組可以比對英文字母或標點符號嗎? 當然!

符號 說明
\d 0-9之間的整數字元
\s 空白鍵、Tab鍵、換行字元
\w 數字、字母和底線字元,相當於[A-Za-z0-9_]
[a-z] a-z小寫字元
[A-Z] A-Z的大寫字元

中括弧內可以不用加上逸出字元符號

r'[0-9.]'

不須寫成r'[0-9\.]

msg = 'Johnson will attend my party.'
pattern = 'John((na)+son)'
規則 = re.compile(pattern)
txt = 規則.search(msg)      # 傳回搜尋結果
msg = 'Johnson will attend my party.'
pattern = 'John((na)+son)'
txt = re.search(pattern, msg)      # 傳回搜尋結果

先re.compile()後再search() 或 findall()

可改成直接用re.search(), re.findall()

msg = 'John, and Johnson will attend my party.'
pattern = '\w+'                    # 不限長度的單字
txt = re.findall(pattern,msg)      # 傳回搜尋結果
import re
# 測試1將字串從句子分離
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party.'
pattern = '\w+'                    # 不限長度的單字
txt = re.findall(pattern,msg)      # 傳回搜尋結果
print(txt)
# 測試2將John開始的字串分離
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party.'
pattern = 'John\w*'                # John開頭的單字
txt = re.findall(pattern,msg)      # 傳回搜尋結果
print(txt)

範例: 將英文句子單字分離,以及將前4字母是John的單字分離

import re

msg = '1 cat, 2 dogs, 3 pigs, 4 swans'
pattern = '\d+\s\w+'
txt = re.findall(pattern,msg)      # 傳回搜尋結果
print(txt)

範例: 找出「數字開頭」「空白或Tab或換行字元分隔」 最後接著「數字字母底線字元」的字串

import re
# 測試1搜尋[aeiouAEIOU]字元
msg = 'John, Johnson, Johnnason and Johnnathan will attend my party.'
pattern = '[aeiouAEIOU]'           
txt = re.findall(pattern,msg)      # 傳回搜尋結果
print(txt)
# 測試2搜尋[2-5.]字元
msg = '1. cat, 2. dogs, 3. pigs, 4. swans'
pattern = '[2-5.]'
txt = re.findall(pattern,msg)      # 傳回搜尋結果
print(txt)

範例: 搜尋aeiou不分大小寫,以及數字2至5與.的組合