National Taiwan University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

台灣大學 AQI 69 24°C PM2.5 18
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 20

正規表達式

用模式描述文字:search、findall 與群組
Interaction實作 25 minute(s)

Learning Objectives

  • 正規表達式(regular expression)語法:特殊字元、集合、重複次數、群組
  • 使用 re 模組:compile()、search()、match()、findall() 與 group()
  • 貪婪與非貪婪比對
  • re 模組的編譯標誌(flag):re.I、re.M、re.S

為什麼需要正規表達式

要找出文字裡「所有電話號碼」「所有 email」「以大寫字母開頭的句子」,用 in 或 find() 只能找固定的字。 正規表達式(regular expression,簡稱 regex)用一串特殊字元描述「長什麼樣子的字串」, 例如 \d{4}-\d{2}-\d{2} 代表「4 個數字、橫線、2 個數字、橫線、2 個數字」這種日期格式。

正規表達式源自 UNIX 環境,現在幾乎所有程式語言與文字編輯器都支援。Python 內建的 re 模組提供 Perl 風格的語法; 樣式寫錯時會引發 re.error 例外。

使用步驟

  1. import re
  2. re.compile(r'樣式') 建立 Regex 物件,樣式記得用 raw string(r'...')
  3. 呼叫 search() 在字串裡找第一個符合的地方,傳回 Match 物件;找不到傳回 None(match() 只比對開頭)
  4. 呼叫 Match 物件的 group() 取得實際符合的字串

為什麼要用 raw string? 一般字串裡 \n 是換行、\t 是 Tab;正規表達式大量使用反斜線(\d、\.), 寫成 r'\d' 反斜線就會原封不動交給 re,不會先被字串轉義吃掉。

>>> len('\n'), len(r'\n')
(1, 2)
>>> print(r'C:\new\table')
C:\new\table

compile → search/match → group

search() 掃描整個字串找第一個符合的位置;match() 只看字串開頭符不符合。兩者找不到時都傳回 None,所以使用前常先用 if 檢查。

特殊字元速查

字元 意思 例子
. 換行以外的任意一個字元 b.t 符合 bat、bet、but
^ 字串開頭 ^up 符合 up、upper 開頭的 up
$ 字串結尾 ear$ 符合 clear、top gear 結尾的 ear
* 前一個元素 0 次以上 ab* 符合 a、ab、abb
+ 前一個元素 1 次以上 ab+ 符合 ab、abb
? 前一個元素 0 或 1 次 ab? 符合 a、ab
{m}、{m,n} 剛好 m 次;m 到 n 次({,n} 最多 n 次、{m,} 至少 m 次) a{3,5} 符合 3~5 個 a
[ ] 集合中的任一字元;- 表示範圍;[^...] 不在集合中 [a-zA-Z]、[0-9]、[^aeiou]
( ) 群組 (ab)+ 符合 ab、abab
\ 跳脫特殊字元,或表示特殊序列 \. 比對句點本身;\d 數字、\w 文字字元、\s 空白

另外,| 代表「或」,例如 cat|dog 符合 cat 或 dog。

另外還有「前後看」:prev(?=next)(後面接著 next 的 prev)、prev(?!next)、(?<=prev)next、(?<!prev)next。

特殊字元逐一試

re.findall(樣式, 字串) 傳回所有符合的字串組成的串列。迴圈每一圈測試一組(樣式, 文字),比對輸出與右邊的 pat、text。也可以在下方改樣式再跑一次。

互動練習:即時正規表達式測試

下面的文字取自 Lab 的 regex_data.txt。依序點選 Lab 練習的樣式,看哪些地方被標出來;勾選 re.M 讓 ^、$ 以「每一列」為單位。也可以自己輸入樣式。

貪婪與非貪婪

*、+、{m,n} 預設是貪婪的:在符合的前提下盡可能多吃字元。後面加上 ? 變成非貪婪:盡可能少吃。比較 <.*> 和 <.*?> 在 '<a> b <c>' 上的結果。

想一想:樣式比對到什麼

1.

下面的程式會印出什麼?

import re
print(re.findall(r"\d+", "a1b22c333"))
2.

下面的程式會印出什麼?

import re
print(re.search(r"<.+>", "<a> b <c>").group())
3.

下面的程式會印出什麼?

import re
print(re.findall(r"^[A-Z]\w*", "Apple banana Cherry"))
第 1 次作答

群組:抓出需要的部分

樣式裡的小括號 ( ) 形成群組。group(0) 或 group() 是整個符合的字串,group(1)、group(2)…依左括號出現的順序編號;groups() 傳回全部群組的元組。

編譯標誌:re.I、re.M、re.S

標誌(flag)放在 re.compile(樣式, 標誌) 或 re.findall(樣式, 字串, 標誌) 的最後,改變比對方式。text 有三列,注意 \n 對 ^ 與 . 的影響。

常用標誌與方法

標誌 說明
re.I(re.IGNORECASE) 忽略大小寫
re.M(re.MULTILINE) 多列模式,^、$ 以每一列為單位
re.S(re.DOTALL) 讓 . 也比對換行字元
re.A(re.ASCII) \w、\d、\s 等只比對 ASCII 字元(預設是 Unicode,所以 \w 也比對中文字)
re.X(re.VERBOSE) 樣式可以分段、加註解,比較好讀

多個標誌用 | 合併,例如 re.I | re.M。

方法 傳回
search(string) 掃描整個字串,第一個符合的 Match 物件;沒有就 None
match(string) 只比對開頭;不符合就 None
fullmatch(string) 整個字串都符合才傳回 Match 物件(適合檢查格式)
findall(string) 所有符合字串組成的串列

prog = re.compile(樣式) 再 prog.search(s),等同 re.search(樣式, s);同一個樣式要用很多次時,先 compile 比較有效率。

Lab:在檔案裡逐列搜尋

這是 Lab 的 regex.py,改成直接讀取工作目錄裡的 regex_data.txt(內容在「檔案」分頁)。目前樣式是 [Tt]he;請把第 4 行依序換成 r'\d'、r'\d{3}'、r'^[A-Z]'、r'^[^A-Z]'、r'b.t'、r'\.$' 再跑,觀察印出哪些列。

想一想:re 的方法

1.

下面的程式會印出什麼?

import re
print(re.match(r"\d+", "abc123"))
2.

下面的程式會印出什麼?

import re
mo = re.search(r"(\w+)@(\w+)", "id: amy@ncu")
print(mo.group(2))
3.

下面的程式會印出什麼?

import re
print(len(re.findall(r"the", "The the THE", re.I)))
第 1 次作答

實作:抓出 email 的帳號與網域

用 re.findall() 搭配兩個群組,找出文字裡所有 email,每個印一行「帳號 網域」。預期輸出:amy ncu.edu.tw 和 bob gmail.com。

你的輸出
預期輸出

重點精華回顧

  • 正規表達式用特殊字元描述字串的樣子;Python 用內建的 re 模組,樣式一律寫成 raw string r'...'。
  • 步驟:re.compile() 建立 Regex 物件 → search()/match() 得到 Match 物件(找不到是 None)→ group() 取得內容。
  • 特殊字元:. 任意字元、^ 開頭、$ 結尾、* + ? {m,n} 重複次數、[ ] 集合、| 或、( ) 群組、\ 跳脫。
  • *、+、{m,n} 預設貪婪(盡可能多),加上 ? 變非貪婪(盡可能少)。
  • findall() 傳回所有符合的字串;樣式有群組時傳回群組組成的元組。
  • 標誌:re.I 忽略大小寫、re.M 多列、re.S 讓 . 比對換行、re.X 可加註解。
這一課完成了!
Next lesson:第三方函數庫
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。