3.4 re - 正規表達式
3.4 re - 正規表達式
正規表達式(Regular Expression,簡稱 regex 或 re)是一種強大的文字模式匹配工具。在 Hook 系統中,主要用於解析 Markdown 連結和驗證輸入格式。
基本用法
re.search() - 搜尋匹配
1import re
2
3text = "Hello, Python 3.11!"
4
5# 搜尋數字
6match = re.search(r'\d+\.\d+', text)
7if match:
8 print(match.group()) # "3.11"re.match() - 從開頭匹配
1import re
2
3text = "Python is great"
4
5# 只從字串開頭匹配
6match = re.match(r'Python', text) # 成功
7match = re.match(r'great', text) # None(不是從開頭開始)re.findall() - 找出所有匹配
1import re
2
3text = "Call 123-456-7890 or 098-765-4321"
4
5# 找出所有電話號碼
6phones = re.findall(r'\d{3}-\d{3}-\d{4}', text)
7# ['123-456-7890', '098-765-4321']re.sub() - 替換
1import re
2
3text = "Hello World"
4
5# 將多個空格替換為單個
6result = re.sub(r'\s+', ' ', text)
7# "Hello World"正規表達式語法
基本字元
| 模式 | 說明 | 範例 |
|---|---|---|
. | 任意字元(除換行) | a.c 匹配 “abc”, “a1c” |
\d | 數字 [0-9] | \d+ 匹配 “123” |
\w | 單字字元 [a-zA-Z0-9_] | \w+ 匹配 “hello” |
\s | 空白字元 | \s+ 匹配空格、tab |
^ | 字串開頭 | ^Hello |
$ | 字串結尾 | World$ |
數量詞
| 模式 | 說明 | 範例 |
|---|---|---|
* | 0 或多次 | a* 匹配 “”, “a”, “aaa” |
+ | 1 或多次 | a+ 匹配 “a”, “aaa” |
? | 0 或 1 次 | a? 匹配 “”, “a” |
{n} | 恰好 n 次 | a{3} 匹配 “aaa” |
{n,m} | n 到 m 次 | a{2,4} 匹配 “aa”, “aaa”, “aaaa” |
群組與擷取
1import re
2
3text = "[Link Text](https://example.com)"
4
5# 使用群組擷取
6match = re.search(r'\[([^\]]+)\]\(([^)]+)\)', text)
7if match:
8 link_text = match.group(1) # "Link Text"
9 link_url = match.group(2) # "https://example.com"實際範例:Markdown 連結檢查
來自 .claude/lib/markdown_link_checker.py:
1import re
2
3class MarkdownLinkChecker:
4 # Markdown 連結正則表達式
5 # 匹配 [text](/python/03-stdlib/regex/target) 格式,排除圖片 
6 INLINE_LINK_PATTERN = re.compile(
7 r'(?<!!)\[([^\]]+)\]\(([^)]+)\)'
8 )
9
10 # 引用式連結定義 [ref]: target
11 REFERENCE_DEF_PATTERN = re.compile(
12 r'^\s*\[([^\]]+)\]:\s*(.+)$',
13 re.MULTILINE
14 )
15
16 # 引用式連結使用 [text][ref]
17 REFERENCE_USE_PATTERN = re.compile(
18 r'\[([^\]]+)\]\[([^\]]+)\]'
19 )模式解析
r'(?<!!)\[([^\]]+)\]\(([^)]+)\)' 解析:
| 部分 | 說明 |
|---|---|
(?<!!) | 負向前瞻,確保前面不是 !(排除圖片) |
\[ | 匹配字面 [ |
([^\]]+) | 群組 1:擷取連結文字(一個或多個非 ] 字元) |
\] | 匹配字面 ] |
\( | 匹配字面 ( |
([^)]+) | 群組 2:擷取連結目標(一個或多個非 ) 字元) |
\) | 匹配字面 ) |
使用範例
1def parse_markdown_links(self, content: str) -> list[dict]:
2 """解析 Markdown 內容中的所有連結"""
3 links = []
4 lines = content.split('\n')
5
6 for line_num, line in enumerate(lines, start=1):
7 # 行內連結 [text](/python/03-stdlib/regex/target)
8 for match in self.INLINE_LINK_PATTERN.finditer(line):
9 links.append({
10 "text": match.group(1),
11 "target": match.group(2),
12 "line": line_num
13 })
14
15 return links編譯正規表達式
對於重複使用的模式,預先編譯可提升效能:
1import re
2
3# 編譯模式
4pattern = re.compile(r'\d+')
5
6# 重複使用
7pattern.search(text1)
8pattern.findall(text2)
9pattern.sub('X', text3)實際應用:Hook 驗證
來自 .claude/lib/hook_validator.py:
1class HookValidator:
2 # 共用模組導入模式
3 HOOK_IO_PATTERNS = [
4 r"from\s+hook_io\s+import",
5 r"from\s+lib\.hook_io\s+import",
6 ]
7
8 # 命名規範模式
9 VALID_NAME_PATTERNS = [
10 r"^[a-z0-9](/python/03-stdlib/regex/[a-z0-9\-_]*[a-z0-9])?\.py$",
11 ]
12
13 def _has_import(self, content: str, patterns: list[str]) -> bool:
14 """檢查是否有符合任一模式的導入"""
15 return any(
16 re.search(pattern, content)
17 for pattern in patterns
18 )
19
20 def check_naming_convention(self, hook_path: Path) -> list:
21 """檢查命名規範"""
22 filename = hook_path.name
23
24 valid_name = any(
25 re.match(pattern, filename)
26 for pattern in self.VALID_NAME_PATTERNS
27 )
28 # ...常用旗標
re.IGNORECASE(忽略大小寫)
1import re
2
3re.search(r'hello', 'Hello World', re.IGNORECASE) # 匹配re.MULTILINE(多行模式)
1import re
2
3text = """line 1
4line 2
5line 3"""
6
7# 每行開頭的 "line"
8matches = re.findall(r'^line', text, re.MULTILINE)
9# ['line', 'line', 'line']re.DOTALL(點號匹配換行)
1import re
2
3text = "start\nmiddle\nend"
4
5# 無 DOTALL:. 不匹配換行
6re.search(r'start.*end', text) # None
7
8# 有 DOTALL:. 匹配換行
9re.search(r'start.*end', text, re.DOTALL) # 匹配外部連結判斷
1class MarkdownLinkChecker:
2 EXTERNAL_PATTERNS = [
3 r'^https?://',
4 r'^mailto:',
5 r'^tel:',
6 r'^ftp://',
7 ]
8
9 def _is_external_link(self, target: str) -> bool:
10 """檢查是否為外部連結"""
11 return any(
12 re.match(pattern, target)
13 for pattern in self.EXTERNAL_PATTERNS
14 )最佳實踐
1. 使用原始字串
1# 好:使用 r'' 避免跳脫問題
2pattern = r'\d+\.\d+'
3
4# 不好:需要雙重跳脫
5pattern = '\\d+\\.\\d+'2. 預編譯重複使用的模式
1# 好:編譯後重複使用
2pattern = re.compile(r'\d+')
3for text in texts:
4 pattern.findall(text)
5
6# 不好:每次都重新編譯
7for text in texts:
8 re.findall(r'\d+', text)3. 使用命名群組
1# 有命名群組:更易讀
2pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})')
3match = pattern.search("2024-01-20")
4if match:
5 print(match.group('year')) # "2024"
6 print(match.group('month')) # "01"思考題
re.search()和re.match()有什麼區別?- 為什麼 Markdown 連結模式使用
(?<!!)負向前瞻? re.compile()的好處是什麼?
實作練習
- 寫一個正規表達式,驗證電子郵件格式
- 從 Python 原始碼中擷取所有函式定義(
def function_name() - 實作一個函式,將 Markdown 標題(
# Title)轉換為 HTML(<h1>Title</h1>)
上一章:subprocess - 執行外部命令 下一章:logging - 日誌系統