Published 4月 08, 2020 by with 0 comment

Automate The Boring Stuff With Python - Chapter 7 – 模式匹配與正則表達式

今天是看這本書Automate the Boring Stuff with Python(Python編程快速上手--讓繁瑣工作自動化)
第7章節(Pattern Matching with Regular Expressions)所做的練習

這章主要講正則表達式的操作

用正則表達式(Regular Expression)來匹配特定的對象
1. 用impore re 來導入正則的模組
2. 用re.complie()來創造一個正則要匹配的對象, 記得要用原始字符串'r'
3. 將這對象用search()的方式導入要查找的文本, 它將返回一個Match的對象
4. 將這Match對象用group()的方式, 返回實際匹配後的字符串

>>> import re
>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')    # r是使用原始字符串, /d是表示0到9任何數字.
>>> mo = phoneNumRegex.search('My phone number is 123-456-7890.')
>>> print('Phone Number Found: ' + mo.group())
Phone Number Found: 123-456-7890
>>>


在第2步驟中, 用多個()號, 可以把gorup創建多個分組.(0)或不代入參數是返回整個文本, (1)是第一組.
>>> import re
>>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')    # 這邊用了兩個()號
>>> mo = phoneNumRegex.search('My phone number is 123-456-7890.')
>>> print('Phone Number Found: ' + mo.group(1))    # 這邊代入group(1), 找出第一組
Phone Number Found: 123
>>>


|符號代表匹配多個選項中的一個,
>>> import re
>>> heroRegex = re.compile(r'Batman|Tina Fey')    # (|)條件為匹配Batman 或 Tina 其中一個
>>> mo1 = heroRegex.search('Batman and Tina Fey.')
>>> mo1.group()
'Batman'    # 雖然兩個都匹配, 但結果會返回第一個出現的匹配.
>>>


?號代表匹配該選項的零次或一次
>>> import re
>>> batRegex = re.compile(r'Bat(wo)?man')    # (wo)?是指?號前的(wo)要匹配它零次或一次
>>> mo1 = batRegex.search('The Adventures of Batman')
>>> mo1.group()
'Batman'
>>>
>>> mo2 = batRegex.search('The Adventures of Batwoman')
>>> mo2.group()
'Batwoman'
>>>


*號代表匹配該選項的零次或多次
>>> import re
>>> batRegex = re.compile(r'Bat(wo)*man')    # (wo)*是指*號前的(wo)要匹配它零次或多次
>>> mo1 = batRegex.search('The Adventures of Batman')
>>> mo1.group()
'Batman'
>>>
>>> mo2 = batRegex.search('The Adventures of Batwoman')
>>> mo2.group()
'Batwoman'
>>>
>>> mo3 = batRegex.search('The Adventures of Batwowowoman')
>>> mo3.group()
'Batwowowoman'
>>>


+號代表匹配該選項的一次或多次(不能為零次)
>>> import re
>>> batRegex = re.compile(r'Bat(wo)+man')    # (wo)+是指+號前的(wo)要匹配它一次或多次
>>> mo1 = batRegex.search('The Adventures fo Batwoman')
>>> mo1.group()
'Batwoman'
>>>
>>> mo2 = batRegex.search('The Adventures fo Batwowowowoman')    # (wo)+ 匹配了多次
>>> mo2.group()
'Batwowowowoman'
>>>
>>> mo3 = batRegex.search('The Adventures fo Batman')    # (wo)+ 匹配零次 故不符合.
>>> mo3 == None
True
>>>


{}號代表匹配該選項的特定次數
>>> import re
>>> haRegex = re.compile(r'(Ha){3}')    # (Ha){3}是指ha要匹配它三次
>>> mo1 =haRegex.search('***HaHaHa***')
>>> mo1.group()
'HaHaHa'
>>>
>>> haRegex= re.compile(r'(Ha){3,5}')    # (Ha){3}是指ha要匹配它最少三次, 最多五次
>>> mo2 = haRegex.search('***HaHaHaHaHaHa***')
>>> mo2.group()
'HaHaHaHaHa'
>>>


貪心和非貪心匹配
因為(Ha){3,5} 可以匹配(Ha)三次, 四次跟五次
但返回結果是五次. 所以Python的正則表達式默認是貪心匹配.
若須要非貪心匹配 可用下列方式
(Ha){3,5}?


用search只返回一個匹配對象, 但用findall可以用列表(list)的方式返回全部的匹配對象
>>> import re
>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
>>> mo1 = phoneNumRegex.search('Cell: 123-456-7890 Work: 098-765-4321')    # 用serach 只返回一個匹配對象
>>> mo1.group()
'123-456-7890'
>>>
>>> mo2 = phoneNumRegex.findall('Cell: 123-456-7890 Work: 098-765-4321') # 用findall可以返回全部的匹配對象
>>> print(mo2)
['123-456-7890', '098-765-4321']
>>>
>>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')    # 兩個()分出兩組
>>> mo3 = phoneNumRegex.findall('Cell: 123-456-7890 Work: 098-765-4321')
>>> print(mo3)
[('123', '456-7890'), ('098', '765-4321')]
>>>


字符分類
\d    0到9的任何數字
\D    除了0到9以外的任何字符
\w    任何字母, 數字或下划線字符 (可以想成是匹配'單詞'的字符)
\W    除了字母, 數字或下划線字符以外的任何字符
\s    空格, 制表符或換行符 (可以想成是匹配'空白'的字符)
\S    除了空格, 制表符或換行符以外的任何字符


透過[]的方式, 可以自定義
[0-5] 只匹配數字0-5
[a-z] 只匹配小寫英文a-z
>>> import re
>>> vowelRegex = re.compile(r'[aeiouAEIOU]')    # 匹配所有大小寫母音
>>> vowelRegex.findall('Apple, Ball, Cat, Dog')
['A', 'e', 'a', 'a', 'o']
>>>


^跟$. ^放在字符開始處, 表示整個文本必須以該字符開始來匹配.
$放在字符結尾處, 表示整個文本以該字符最後來匹配.
>>> import re
>>> beginsWithHello = re.compile(r'^Hello')    # Hello 必須在整個文本的開頭處
>>> mo1 = beginsWithHello.search('Hello World!')    # 該Hello有在整個文本開頭處, 所以有符合
>>> mo1.group()
'Hello'
>>>
mo2 = beginWithsHello.search('He say Hello World!')    # 該Hello沒有在整個文本開頭處, 所以沒有符合
>>> print(mo2)
None
>>>
>>> endsWithNum = re.compile(r'\d$') #任一個數字必須在整個文本的結尾處
>>> mo2 = endsWithNum.search('The Answer is 42')    #該數字2有在整個文本結尾處, 所以有符合
>>> mo2.group()
'2'
>>>


.通配字符. 用.可以匹配除了換行符以外的所有字符(只匹配一個字符)
>>> import re
>>> atRegex = re.compile(r'.at')    # 匹配除了換行符以外所有接上at的一個字符
>>> mo1 = atRegex.findall('The cat in the hat sat on the flat mat.')    #flat at前有兩個 所以不算
>>> mo1
['cat', 'hat', 'sat', 'lat', 'mat']
>>>


用.*可以匹配除了換行符以外的所有字符的零次或多次
>>> import re
>>> nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)')
>>> mo1 = nameRegex.search('First Name: Peter Last Name: Chang')
>>> mo1.group(1)
'Peter'
>>> mo1.group(2)
'Chang'
>>>


用re.DOTALL做為re.compile()的第二個參數, 可以讓.通配字符匹配包括換行符的所有字符(只匹配一個字符)
>>> import re
>>> mo1 = newLineRegex.search('Server the public trust.\nProtect the innocent.')    # 用re.DOTALL匹配包括換行符, 用*匹配所有字
>>> mo1.group()
'Server the public trust.\nProtect the innocent.'
>>>


用re.IGNORECASE或re.I做為re.compile()的第二個參數, 可以忽略大小寫的匹配
>>> import re
>>> vowelRegex = re.compile(r'[aeiou]', re.I)
>>> vowelRegex.findall('Apple, Ball, Cat, Dog, Eat')
['A', 'e', 'a', 'a', 'o', 'E', 'a']
>>>


用sub()來替換字符串, sub()傳入兩個參數. 第一個是要取代為的字符串, 第二個是被取代的匹配.
>>> import re
>>> namesRegex = re.compile(r'Agent \w+')    # 要匹配 開頭是Agent 後面是零次或一次的單詞
>>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob')    # 把匹配的字符取代為CENSORED
'CENSORED gave the secret documents to CENSORED'
>>>



用re.VERBOSE來忽略正則表達式字符串中的空白符跟注釋, 來管理規則複雜的正則表達式
>>> phoneRegex = re.compile(r'''(
    (\d{3}|\(\d{3}\))?                # area code: 123 or (123) or none
    (\s|-|\.)?                        # separator: - or  or . or none
    \d{3}                            # first 3 digits: 123
    (\s|-|\.)                        # separator: - or  or .
    \d{4}                            # last 4 digits: 1234
    (\s*(ext|x|ext.)\s*\d{2,5})?    # extension: (ext or x or ext.) 12 or 12345
    )''', re.VERBOSE
    )
>>>


RE符號整理
用'?'號表示前面的分組匹配零次或一次
用'*'號表示前面的分組匹配零次或多次
用'+'號表示前面的分組匹配一次或多次
用'{n}'號表示前面的分組重複n次數
用'{n,}'號表示前面的分組重複n次數或更多次
用'{,m}'號表示前面的分組重複零次到m次
用'{n,m}'號表示前面的分組重複n次到m次
用'^spam'表示字符串必須是spam開頭
用'spam$'表示字符串必須是spam結束
用'.'匹配任意一個字符. 換行符除外
用'(.*)'表示任意的全部的字符. 換行符除外
\d, \w, \s 分別匹配數字, 單詞, 空格
\D, \W, \S 分別匹配非數字, 非單詞, 非空格
[a-zA-Z0-9]匹配所有大小寫字母和數字
[^abc]匹配不在方括號內的任意字符(除了abc以外的字符)


章節7習題, 用Notepad++打下列的程式碼,
另存為7_18_1.py. 我附上中文注釋方便好讀.
# -*- coding: UTF-8 -*-
# http://juilin77.blogspot.com/
# v20190214
# Automate The Boring Stuff With Python - Chapter 7

# ========== 7.18.1 ==========
import re
pw = input('Password: ')
Check = re.compile(r'[a-zA-Z0-9]{8,}')
if Check.search(pw):
    print('Good, your password is strong!')
else:
    print('The password is at least eight characters long, contains both uppercase and lowercase characters, and has at least one digit.')


Reference:Automate the Boring Stuff with Python
Python編程快速上手--讓繁瑣工作自動化
ISBN-10: B01N6B9BSA
https://www.amazon.com/Python%E7%BC%96%E7%A8%8B%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B-%E8%AE%A9%E7%B9%81%E7%90%90%E5%B7%A5%E4%BD%9C%E8%87%AA%E5%8A%A8%E5%8C%96-%E7%BE%8E-Al-Sweigart%EF%BC%88%E6%96%AF%E7%BB%B4%E5%8A%A0%E7%89%B9%EF%BC%89/dp/B01I0XN8XY/ref=sr_1_1?ie=UTF8&qid=1543814481&sr=8-1&keywords=9787115422699

官網:
https://automatetheboringstuff.com/


最初發表 / 最後更新: 2020.04.08 / 2020.04.08

0 comments:

張貼留言