返回文章列表

Humre 提升正規表示式可讀性

本文介紹 Humre 函式庫,它提供更簡潔易懂的語法來撰寫正規表示式,提升程式碼可讀性及維護性。文章以電話號碼正規表示式重構為例,比較傳統正規表示式與 Humre 的差異,並說明 Humre 的優點,包含易讀性、模組化及降低錯誤率。此外,文章也涵蓋正規表示式的基本概念、特殊字元、量詞、字元類別以及 Python 中

程式開發 正規表示式

正規表示式是處理字串模式匹配的強大工具,但其語法複雜,不易閱讀和維護。Humre 函式庫提供更直觀的語法,簡化正規表示式的撰寫,使其更易於理解和維護。以電話號碼匹配為例,使用 Humre 重寫後,程式碼結構更清晰,更容易理解各個部分的功能。Humre 的優點在於提升可讀性、模組化設計以及減少錯誤的產生。除了 Humre,文章也涵蓋正規表示式的基礎知識,例如特殊字元、量詞、字元類別等,並說明如何在 Python 中使用 re 模組操作正規表示式。此外,文章也介紹了 Python 的 pathlib 模組,示範如何使用 Path 物件來處理檔案路徑和目錄,包含絕對路徑、相對路徑、路徑屬性以及路徑操作等。

使用 Humre 重寫正規表示式

Humre 是一個方便的函式庫,讓我們可以用更易讀的方式來撰寫正規表示式。讓我們來看看如何使用 Humre 來重寫一個電話號碼的正規表示式。

原始電話號碼正規表示式

在之前的電話號碼提取器專案中,我們使用了以下的正規表示式:

import re

phone_regex = re.compile(r'(\d{3})[\s.-]?(\d{3})[\s.-]?(\d{4})')

這個正規表示式可以匹配大部分的電話號碼格式,但是它的可讀性不是很好。

使用 Humre 重寫電話號碼正規表示式

現在,讓我們使用 Humre 來重寫這個正規表示式:

from humre import *

phone_regex = group(
    optional_group(either(exactly(3, DIGIT),  # 前三位數字
                          OPEN_PAREN + exactly(3, DIGIT) + CLOSE_PAREN)),  # 或者括號包圍的三位數字
    optional(group_either(WHITESPACE, '-', PERIOD)),  # 可選的分隔符
    group(exactly(3, DIGIT)),  # 中間三位數字
    group_either(WHITESPACE, '-', PERIOD),  # 分隔符
    group(exactly(4, DIGIT))  # 後四位數字
)

在這個版本中,我們使用了 Humre 的 groupoptional_groupeitherexactly 函式來定義電話號碼的結構。這使得正規表示式更加易於理解和維護。

Humre 的優點

使用 Humre 有幾個優點:

  • 易讀性:Humre 的語法使得正規表示式更加易於理解和維護。
  • 模組化:Humre 允許你將複雜的正規表示式分解成小的、可重用的部分。
  • 減少錯誤:Humre 的語法可以幫助你避免常見的正規表示式錯誤。

正規表示式與 Humre

正規表示式(Regular Expressions)是一種強大的工具,能夠用於搜尋和匹配特定的字串模式。然而,它的語法複雜且難以閱讀,尤其對於初學者來說。為瞭解決這個問題,Humre 這個工具被提出來,以更易於理解和編寫的方式來定義正規表示式。

Humre 的優點

Humre 提供了一種更直觀和易於閱讀的方式來定義正規表示式。它允許使用者使用更自然的語法來描述他們想要匹配的模式,而不是使用傳統的正規表示式語法。這使得 Humre 成為了一個有用的工具,尤其是在需要與他人合作或需要維護複雜的正規表示式時。

Humre 的工作原理

Humre 透過將使用者定義的模式轉換為等效的正規表示式字串來工作。這意味著 Humre 可以輕鬆地與現有的支援正規表示式的工具和函式庫一起使用。使用 Humre,可以編寫出更易於理解和維護的正規表示式程式碼,並且可以輕鬆地與他人分享和合作。

Humre 與傳統正規表示式的比較

傳統的正規表示式語法可能很難閱讀和理解,尤其是對於那些不熟悉它的人。相比之下,Humre 提供了一種更簡潔和直觀的方式來定義模式。然而,傳統的正規表示式語法仍然被廣泛支援和使用,尤其是在需要高效能和複雜匹配的情況下。

練習題答案

  1. re.compile() 函式傳回 Regex 物件。
  2. Raw 字串通常用於建立 Regex 物件,以避免反斜線被視為特殊字元。
  3. search() 方法傳回 Match 物件,如果找到匹配;否則傳回 None
  4. 可以使用 Match 物件的 group() 方法來取得實際匹配的字串。
  5. 在由 r'(\d\d\d)-(\d\d\d-\d\d\d\d)' 建立的正規表示式中,群組 0 涵蓋整個匹配字串,群組 1 涵蓋第一個 (\d\d\d),群組 2 涵蓋第二個 (\d\d\d-\d\d\d\d)
  6. 要指定實際的括號和句點字元,可以在字元前面加上反斜線 (\)。
  7. findall() 方法傳回字串列表或字串元組列表,取決於正規表示式中是否使用了群組。
  8. | 字元在正規表示式中表示「或」運算。
  9. ? 字元在正規表示式中可以表示「0 或 1 次」或「非貪婪匹配」,取決於其使用方式。

10. 正規表示式中 + 和 * 字元的差異

在正規表示式中,+ 和 * 是兩種不同的量詞(quantifier)。+ 用於匹配前面的模式至少一次,而 * 用於匹配前面的模式零次或多次。

11. 正規表示式中 {3} 和 {3,5} 的差異

{3} 和 {3,5} 都是用於指定匹配次數的量詞,但它們的作用不同。{3} 表示恰好匹配 3 次,而 {3,5} 表示匹配 3 到 5 次。

12. 正規表示式中 \d、\w 和 \s 的含義

在正規表示式中,\d、\w 和 \s 是三種常用的簡寫字元類別。

  • \d 匹配任何數字(等同於 [0-9])。
  • \w 匹配任何單詞字元(等同於 [a-zA-Z0-9_])。
  • \s 匹配任何空白字元(等同於 [\r\n\t\f\v ])。

13. 正規表示式中 \D、\W 和 \S 的含義

在正規表示式中,\D、\W 和 \S 是三種常用的簡寫字元類別,它們與 \d、\w 和 \s 相反。

  • \D 匹配任何非數字字元(等同於 [^0-9])。
  • \W 匹配任何非單詞字元(等同於 [^a-zA-Z0-9_])。
  • \S 匹配任何非空白字元(等同於 [^\r\n\t\f\v ])。

14. 正規表示式中.* 和.*? 的差異

.* 和.? 都是用於匹配任意字元的模式,但它們的貪婪程度不同。. 是貪婪匹配,會盡可能多地匹配字元,而.*? 是非貪婪匹配,會盡可能少地匹配字元。

15. 匹配所有數字和小寫字母的字元類別語法

要匹配所有數字和小寫字母,可以使用字元類別 [0-9a-z]。

16. 如何使正規表示式查詢不區分大小寫

可以使用標誌(flag)來使正規表示式查詢不區分大小寫,例如在 Python 中使用 re.IGNORECASE 標誌。

17.. 字元的正常匹配和特殊情況

. 字元通常匹配除換行符之外的任意單個字元。但是在某些特殊情況下,. 可能會有不同的行為,例如當使用 re.DOTALL 標誌時,. 會匹配包括換行符在內的所有字元。

實踐程式

強密碼檢測

可以使用正規表示式來檢測密碼強度。一個強密碼應該至少 8 個字元長,包含大寫和小寫字母,以及至少一個數字。可以使用多個正規表示式模式來檢測這些規則。

Regex 版本的 strip() 方法

可以使用正規表示式來實作 strip() 方法的功能。如果沒有提供其他引數,則從字串的開始和結束處移除空白字元。否則,移除指定的字元。

讀取和寫入檔案

變數可以用於儲存程式執行期間的資料,但如果需要儲存資料以便在程式結束後仍可使用,則需要將其儲存到檔案中。檔案的內容可以被視為一個字串值,其大小可能達到數十億位元組。在本章中,您將學習如何使用 Python 建立、讀取和儲存硬碟上的檔案。

檔案和檔案路徑

檔案有兩個關鍵屬性:檔名(通常寫為一個單詞)和路徑。路徑指定了檔案在電腦上的位置。例如,在我的 Windows 筆記型電腦上有一個名為 project.docx 的檔案,其路徑為 C:\Users\Al\Documents。檔名後面的最後一個句點之後的部分稱為副檔名,它告訴您檔案的型別。project.docx 是一個 Word 檔案,而 Users、Al 和 Documents 都是指目錄(也稱為資料夾)。資料夾可以包含檔案和其他資料夾(稱為子資料夾)。例如,project.docx 位於 Documents 資料夾中,Documents 資料夾位於 Al 資料夾中,Al 資料夾位於 Users 資料夾中。圖 10-1 顯示了此資料夾組織結構。

附加捲

附加捲,如 DVD 驅動器或 USB 快閃記憶體驅動器,在不同的作業系統上會以不同的方式顯示。在 Windows 上,它們會顯示為新的帶字母的根驅動器,例如 D:\ 或 E:\。在 macOS 上,它們會顯示為 /Volumes 資料夾下的新資料夾。在 Linux 上,它們會顯示為 /mnt(“mount”)資料夾下的新資料夾。此外,請注意,雖然 Windows 和 macOS 上的資料夾名稱和檔名不區分大小寫,但 Linux 上是區分大小寫的。

路徑標準化與處理

在不同作業系統中,路徑的分隔符號可能會有所不同。Windows 使用反斜線(\)作為分隔符號,而 macOS 和 Linux 則使用正斜線(/)作為分隔符號。為了確保跨平臺的相容性,Python 的 pathlib 模組提供了一個統一的方式來處理路徑。

使用 Path 類別

Path 類別是 pathlib 模組中的核心類別,負責處理路徑的建立、修改和查詢。以下是 Path 類別的一些基本用法:

from pathlib import Path

# 建立一個 Path 物件
path = Path('spam', 'bacon', 'eggs')

# 將 Path 物件轉換為字串
print(str(path))  # 輸出:spam\bacon\eggs(在 Windows 中)

路徑分隔符號

如前所述,Windows 使用反斜線(\)作為分隔符號,而 macOS 和 Linux 則使用正斜線(/)作為分隔符號。為了避免跨平臺的相容性問題,建議在 Python 程式碼中使用正斜線(/)作為分隔符號。

# 使用正斜線(/)作為分隔符號
path = Path('spam', 'bacon', 'eggs')

# 輸出:spam/bacon/eggs(在 Windows 中)
print(path)

路徑結合

Path 類別提供了一個 / 運算元來結合路徑。以下是結合路徑的一些基本用法:

# 結合路徑
path = Path('spam') / 'bacon' / 'eggs'

# 輸出:spam/bacon/eggs(在 Windows 中)
print(path)

路徑查詢

Path 類別提供了一些方法來查詢路徑的屬性,例如 exists()is_dir()is_file() 等。

# 查詢路徑是否存在
path = Path('spam')
if path.exists():
    print("路徑存在")
else:
    print("路徑不存在")

目錄與路徑操作

在 Python 中,理解目錄和路徑是非常重要的,尤其是在處理檔案和資料夾時。這篇文章將介紹如何使用 Python 來操作目錄和路徑。

目錄和路徑的基本概念

在電腦中,每個檔案和資料夾都有一個唯一的路徑。路徑可以是絕對路徑,也可以是相對路徑。絕對路徑是從根目錄開始的完整路徑,而相對路徑是從當前工作目錄開始的路徑。

取得當前工作目錄

Python 提供了兩種方式來取得當前工作目錄:os.getcwd()Path.cwd()。其中,os.getcwd() 是舊的方式,而 Path.cwd() 是新的方式,傳回一個 Path 物件。

import os
from pathlib import Path

# 使用 os.getcwd() 取得當前工作目錄
print(os.getcwd())

# 使用 Path.cwd() 取得當前工作目錄
print(Path.cwd())

修改當前工作目錄

如果你想要修改當前工作目錄,可以使用 os.chdir() 函式。注意,os.chdir() 只接受字串作為引數,如果你想要使用 Path 物件,需要先將其轉換為字串。

import os
from pathlib import Path

# 修改當前工作目錄
os.chdir('C:\\Windows\\System32')

# 驗證當前工作目錄
print(Path.cwd())

處理不存在的目錄

如果你嘗試修改到一個不存在的目錄,Python 會丟擲一個 FileNotFoundError

import os

# 嘗試修改到一個不存在的目錄
try:
    os.chdir('C:/ThisFolderDoesNotExist')
except FileNotFoundError as e:
    print(e)

路徑操作

Python 的 pathlib 模組提供了很多方便的函式來操作路徑。例如,你可以使用 / 運算子來連線路徑。

from pathlib import Path

# 連線路徑
path = Path('C:\\Windows') / 'System32'
print(path)
圖表翻譯:

內容解密:

上述程式碼展示瞭如何使用 Python 來操作目錄和路徑。首先,我們匯入了 ospathlib 模組。然後,我們使用 os.getcwd()Path.cwd() 來取得當前工作目錄。接下來,我們使用 os.chdir() 來修改當前工作目錄,並驗證了新的工作目錄。最後,我們嘗試修改到一個不存在的目錄,並捕捉了 FileNotFoundError。同時,我們還展示瞭如何使用 / 運算子來連線路徑。

存取主目錄

所有使用者都有一個自己的檔案夾,稱為主目錄或家目錄。您可以使用 Path.home() 函式取得主目錄的路徑物件:

from pathlib import Path
print(Path.home())

這將輸出使用者的主目錄路徑,例如 WindowsPath('C:/Users/Al')

根據作業系統的不同,主目錄位於不同的位置:

  • Windows:主目錄位於 C:\Users 下。
  • macOS:主目錄位於 /Users 下。
  • Linux:主目錄通常位於 /home 下。

您的指令碼幾乎可以肯定具有讀取和寫入您主目錄下檔案的許可權,因此這是放置您的 Python 程式將要處理的檔案的理想位置。

指定絕對路徑與相對路徑

有兩種方式指設定檔案路徑:

  • 絕對路徑:始終以根目錄開頭(Windows 上為 C:\,macOS 和 Linux 上為 /)。
  • 相對路徑:相對於程式的目前工作目錄。

在 Windows 上,C:\ 是主硬碟的根目錄。這種命名方式可以追溯到 1960 年代,當時電腦具有兩個標有 A: 和 B: 的軟碟機。Windows 上的 USB 快閃記憶體和 DVD 光碟機被分配到 D: 和更高的字母。使用其中一個磁碟機作為根目錄即可存取該儲存媒體上的檔案。

還有兩個特殊的目錄名稱:...。這些不是真正的目錄,而是可以在檔案路徑中使用的特殊名稱。單個句點 (.) 代表目前的目錄,而兩個句點 (..) 代表父目錄。

建立新目錄

您的程式可以使用 os.makedirs() 函式建立新目錄。例如:

import os
os.makedirs('C:\\delicious\\walnut\\waffles')

這將建立 C:\delicious 目錄、C:\delicious\walnut 目錄和 C:\delicious\walnut\waffles 目錄。

或者,您可以使用 Path 物件的 mkdir() 方法建立新目錄:

from pathlib import Path
Path(r'C:\Users\Al\spam').mkdir()

注意,mkdir() 只能建立一個目錄,除非您傳遞 parents=True,在這種情況下,它將建立所有必要的父目錄。

處理絕對路徑和相對路徑

您可以使用 is_absolute() 方法檢查 Path 物件是否代表絕對路徑或相對路徑:

from pathlib import Path
print(Path.cwd().is_absolute())  # True
print(Path('spam/bacon/eggs').is_absolute())  # False

要從相對路徑取得絕對路徑,您可以在相對路徑前面加上 Path.cwd()

from pathlib import Path
relative_path = Path('spam/bacon/eggs')
absolute_path = Path.cwd() / relative_path
print(absolute_path)

或者,您可以使用 absolute() 方法:

from pathlib import Path
relative_path = Path('spam/bacon/eggs')
absolute_path = relative_path.absolute()
print(absolute_path)

內容解密:

上述程式碼示範瞭如何使用 Path 物件和 os 模組處理檔案路徑和目錄。瞭解絕對路徑和相對路徑之間的差異以及如何建立新目錄和檢查路徑是否為絕對路徑或相對路徑是非常重要的。

圖表翻譯:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Humre 提升正規表示式可讀性

package "正規表示式" {
    package "基本語法" {
        component [字元類 [abc]] as char_class
        component [量詞 * + ?] as quantifier
        component [錨點 ^ $] as anchor
    }

    package "進階功能" {
        component [群組 ()] as group
        component [後向參考 \1] as backref
        component [前瞻後顧] as lookahead
    }

    package "Python re 模組" {
        component [re.match()] as match
        component [re.search()] as search
        component [re.findall()] as findall
        component [re.sub()] as sub
    }
}

char_class --> quantifier : 重複匹配
quantifier --> anchor : 位置定位
group --> backref : 捕獲參考
match --> search : 模式搜尋
search --> findall : 全部匹配
findall --> sub : 取代操作

note right of lookahead
  (?=...) 正向前瞻
  (?!...) 負向前瞻
  (?<=...) 正向後顧
end note

@enduml

上述 Plantuml 圖表描述了處理檔案路徑和目錄的流程,包括取得主目錄、建立新目錄、檢查路徑是否為絕對路徑或相對路徑以及從相對路徑取得絕對路徑。

使用 Pathlib 處理檔案路徑

在 Python 中,pathlib 模組提供了一種簡單且直觀的方式來處理檔案路徑。您可以使用 Path 類別來代表檔案路徑,並使用其方法和屬性來操作路徑。

絕對路徑和相對路徑

Path 物件可以代表絕對路徑和相對路徑。絕對路徑是從根目錄開始的完整路徑,而相對路徑是相對於某個目錄的路徑。

from pathlib import Path

# 相對路徑
relative_path = Path('my/relative/path')
print(relative_path)  # Output: my/relative/path

# 絕對路徑
absolute_path = Path.cwd() / Path('my/relative/path')
print(absolute_path)  # Output: C:/Users/Al/Desktop/my/relative/path

取得檔案路徑的各部分

您可以使用 Path 物件的屬性來取得檔案路徑的各部分,例如根目錄、磁碟機、父目錄、檔案名稱等。

from pathlib import Path

p = Path('C:/Users/Al/spam.txt')

print(p.anchor)  # Output: C:\
print(p.drive)  # Output: C
print(p.parent)  # Output: C:/Users/Al
print(p.name)  # Output: spam.txt
print(p.stem)  # Output: spam
print(p.suffix)  # Output:.txt

組合路徑

您可以使用 / 運算元來組合路徑。

from pathlib import Path

p1 = Path('C:/Users/Al')
p2 = Path('spam.txt')

p = p1 / p2
print(p)  # Output: C:/Users/Al/spam.txt

取得絕對路徑

您可以使用 absolute() 方法來取得絕對路徑。

from pathlib import Path

p = Path('my/relative/path')
absolute_path = p.absolute()
print(absolute_path)  # Output: C:/Users/Al/Desktop/my/relative/path

路徑操作與屬性存取

在處理檔案路徑時,瞭解如何存取和操作路徑的各個部分至關重要。Python 的 pathlib 模組提供了一個名為 Path 的類別,用於表示檔案系統中的路徑。透過 Path 物件,我們可以輕鬆地存取路徑的各個屬性,如驅動器、根目錄、父目錄、檔名、檔案字尾等。

路徑屬性

  • drive: 傳回路徑的驅動器部分,如 'C:'
  • root: 傳回路徑的根目錄部分,如 '\' 在 Windows 中或 '/' 在 Unix/Linux 中。
  • name: 傳回路徑的檔名部分,包括字尾,如 'spam.txt'
  • stem: 傳回路徑的檔名部分,不包括字尾,如 'spam'
  • suffix: 傳回路徑的檔案字尾部分,如 '.txt'
  • parts: 傳回一個元組,包含路徑的各個部分,如 ('C:\\', 'Users', 'Al', 'spam.txt')
  • parent: 傳回一個 Path 物件,表示當前路徑的父目錄。
  • parents: 傳回一個可迭代物件,包含當前路徑的所有祖先目錄。

示例

from pathlib import Path

# 建立一個 Path 物件
p = Path('C:/Users/Al/spam.txt')

# 存取路徑屬性
print(p.drive)  # 輸出:C:
print(p.name)   # 輸出:spam.txt
print(p.stem)   # 輸出:spam
print(p.suffix) # 輸出:.txt

# 存取路徑部分
print(p.parts)  # 輸出:('C:\\', 'Users', 'Al', 'spam.txt')
print(p.parts[3]) # 輸出:spam.txt

# 存取父目錄
print(p.parent)  # 輸出:C:\Users\Al

# 存取祖先目錄
for parent in p.parents:
    print(parent)

透過 pathlib 模組的 Path 類別,我們可以方便地存取和操作檔案系統中的路徑。瞭解這些屬性和方法可以幫助我們更好地處理檔案路徑,提高程式碼的可讀性和效率。

從技術架構視角來看,Humre 的出現,為正規表示式的撰寫提供了一種更具結構化和可讀性的方案。透過將複雜的表示式分解成更小的、語義更清晰的元件,Humre 大大降低了理解和維護正規表示式的難度。分析其核心機制,Humre 本質上是將更友好的語法糖轉譯成標準的正規表示式,並未引入全新的匹配引擎,因此效能損耗微乎其微。然而,Humre 的語法雖然簡潔,但仍需一定的學習成本。對於已經熟悉傳統正規表示式語法的開發者而言,是否需要轉換到 Humre 需要權衡其帶來的效益和學習成本。展望未來,隨著程式碼可讀性要求的提升,預期類別似 Humre 的工具將會更廣泛地被應用於簡化複雜的字串處理任務。對於追求程式碼品質的團隊,建議評估 Humre 並探索其在專案中的應用價值。