返回文章列表

Python 字串處理與正規表示式實務應用

本文探討 Python 字串處理技巧,涵蓋索引、切片、常用方法以及正規表示式的應用,並以解析 `/etc/fstab` 檔案及處理 JSON 資料為例,示範如何結合 `httpx` 函式庫進行網頁 API 自動化操作,包含 RESTful API 的 CRUD 模型與客戶端物件的最佳實務。

程式語言 Web 開發

Python 提供豐富的字串操作方法,從基本的索引切片到進階的正規表示式,都能有效處理文字資料。startswithendswith 等方法簡化了字串比對,而 stripsplit 則方便處理檔案格式,例如解析 /etc/fstab 設定。join 方法則能有效率地合併字串序列,避免中間儲存的效能損耗。正規表示式則提供更強大的模式匹配能力,結合 re 模組,可以處理複雜的字串搜尋、驗證和修改任務。理解正規表示式的基礎語法,例如字元類別、重複修飾符和分組,對於提升程式碼效率至關重要。此外,httpx 函式庫簡化了網頁 API 的互動,其內建的 JSON 序列化和反序列化功能,以及同步和非同步操作支援,使其成為自動化網頁操作的利器。使用顯式客戶端物件管理連線和設定引數,能提升程式碼的可維護性和彈性。最後,結合 RESTful API 的 CRUD 模型,能更有效率地操作網路資源。

字串處理與正規表示式

在 Python 中,字串是一種常見的資料型別,具有豐富的操作方法。除了基本的序列介面外,字串還有許多專屬的方法,特別是在文字分析方面非常有用。

字串索引與切片

Python 的字串支援索引和切片操作,可以方便地存取和操作字串中的字元。

a = "hello"
print(a[:-3])  # 輸出 'he'
print(a[::-1])  # 輸出 'olleh'

內容解密:

  1. a[:-3] 表示從字串 a 的開始到倒數第三個字元之前的切片,因此輸出 'he'
  2. a[::-1] 表示以步長為 -1 的方式遍歷整個字串,實作了字串的反轉,因此輸出 'olleh'

字串方法

字串具有許多實用的方法,如 startswithendswithstripsplit 等。

print("hello world".endswith("world"))  # 輸出 True
print("hello world".endswith(("universe", "world")))  # 輸出 True
filename = "example.tar.gz"
print(filename.endswith((".tgz", ".tar.gz")))  # 檢查檔案是否具有特定的副檔名

內容解密:

  1. endswith 方法檢查字串是否以指定的字尾結尾,可以傳入單一字串或元組。
  2. 在檢查檔案副檔名的例子中,傳入元組 (".tgz", ".tar.gz") 以匹配多種可能的副檔名。

解析 /etc/fstab 檔案

下面是一個解析 /etc/fstab 檔案的例子,展示瞭如何使用 stripsplit 方法。

with open("/etc/fstab") as fpin:
    for line in fpin:
        line = line.rstrip('\n')
        line = line.split('#', 1)[0]
        if not line:
            continue
        device, path, fstype, options, freq, passno = line.split()
        print(f"Mounting {device} on {path}")

內容解密:

  1. rstrip('\n') 去除每行末尾的換行符。
  2. split('#', 1)[0] 去除註解內容,只保留實際的設定行。
  3. split() 用於按空白字元分割行內容,將其解析為各個欄位。
  4. 使用格式化字串輸出結果,使其更易讀。

join 方法

join 方法用於將可迭代的字串物件連線起來。

print(' '.join(["hello", "world"]))  # 輸出 "hello world"
names = dict(hello=1, world=2)
print(' '.join(names))  # 輸出 "hello world"
print('-*-'.join(str(x) for x in range(3)))  # 輸出 "0-*-1-*-2"

內容解密:

  1. ' '.join(["hello", "world"]) 將列表中的字串以空格連線。
  2. 對字典使用 join 時,會將字典的鍵連線起來。
  3. 可以傳入生成器表示式,動態生成序列並連線,避免中間儲存。

正規表示式

正規表示式是一種用於描述字串模式的特殊語法,在 Python 中由 re 模組實作。

import re

pattern = re.compile(r"hello")
print(pattern.match("hello world"))  # 匹配成功

內容解密:

  1. 使用 re.compile 編譯正規表示式,生成一個模式物件。
  2. 可以使用該模式物件進行匹配、查詢和替換等操作。

正規表示式基礎

正規表示式的基本元素包括字元匹配、字元類別和重複修飾符。

import re

# 匹配 "hello"
pattern = re.compile(r"hello")
# 匹配 "hell" 後跟任意一個字元
pattern = re.compile(r"hell.")
# 匹配 "woman" 或 "women"
pattern = re.compile(r"wom[ae]n")
# 匹配任意數字
pattern = re.compile(r"[0-9]")

內容解密:

  1. 大多數字符代表其本身,如 hello 匹配字串 "hello"
  2. . 代表任意一個字元。
  3. 方括號 [] 用於定義字元類別,如 [0-9] 匹配任意數字。
  4. 重複修飾符如 ?*+ 用於指定前面的表示式重複的次數。

分組與非捕捉分組

正規表示式支援分組,可以用於捕捉匹配的部分或將部分表示式視為一個整體。

import re

# 使用非捕捉分組匹配 "hello-3" 或 "hello-world-5"
pattern = re.compile(r"(?:[a-z]{2,5}-){1,4}[0-9]")

內容解密:

  1. (?:...) 表示非捕捉分組,將括號內的表示式視為一個整體,但不捕捉匹配結果。
  2. 該模式匹配特定的格式,如 "hello-3""hello-world-5"

正規表示式與JSON資料處理

正規表示式是一種強大的字串處理工具,廣泛應用於文字搜尋、驗證和修改等任務中。Python的re模組提供了正規表示式的支援,使得開發者能夠以程式化的方式處理複雜的字串模式。

正規表示式的基本概念

正規表示式是一種特殊的字串,用於描述或匹配一系列符合特定規則的字串。它們可以用來檢查一個字串是否包含某種特定的子字串,或者用來從字串中提取符合特定模式的子字串。

建立正規表示式物件

在Python中,首先需要使用re.compile()函式將一個正規表示式模式編譯成一個正規表示式物件。

import re
reobj = re.compile('ab+a')

搜尋與匹配

正規表示式物件提供了match()search()兩種方法來進行字串的匹配。match()方法檢查字串的開頭是否符合模式,而search()方法則在整個字串中搜尋第一個符合模式的子字串。

m = reobj.search('hello abba world')
print(m.group())  # 輸出:abba

分組與命名分組

正規表示式支援分組(grouping),可以捕捉符合模式的子字串。透過使用括號(),可以定義分組,並在匹配後提取這些分組。

reobj = re.compile('(a)(b+)(a)')
m = reobj.search('hello abba world')
print(m.group(1))  # 輸出:a
print(m.group(2))  # 輸出:bb
print(m.group(3))  # 輸出:a

詳細解說:

  1. (a):第一個分組,匹配字元a
  2. (b+):第二個分組,匹配一個或多個字元b
  3. (a):第三個分組,再次匹配字元a

此外,還可以為分組命名,以提高程式碼的可讀性。

reobj = re.compile('(?P<prefix>a)(?P<body>b+)(?P<suffix>a)')
m = reobj.search('hello abba world')
print(m.group('prefix'))  # 輸出:a
print(m.group('body'))    # 輸出:bb
print(m.group('suffix'))  # 輸出:a

詳細解說:

  1. (?P<prefix>a):命名為prefix的分組,匹配字元a
  2. (?P<body>b+):命名為body的分組,匹配一個或多個字元b
  3. (?P<suffix>a):命名為suffix的分組,匹配字元a

正規表示式的詳細模式

使用re.VERBOSE旗標,可以在正規表示式中加入空白和註解,使其更易讀。

reobj = re.compile(r"""
    (?P<prefix>a)  # 起始字元,總是一個 a
    (?P<body>b+)   # 中間部分,可以是任意數量的 b
    (?P<suffix>a)  # 結尾字元,總是一個 a
""", re.VERBOSE)
m = reobj.search("hello abba world")
print(m.groups())  # 輸出:('a', 'bb', 'a')

詳細解說:

  1. (?P<prefix>a):匹配起始字元a
  2. (?P<body>b+):匹配中間的一個或多個字元b
  3. (?P<suffix>a):匹配結尾字元a

JSON資料處理

JSON(JavaScript Object Notation)是一種輕量級的資料交換格式,易於閱讀和編寫,也易於機器解析和生成。Python的json模組提供了JSON資料的編碼和解碼功能。

JSON基本操作

使用json.dumps()函式可以將Python物件轉換為JSON格式的字串,而json.loads()函式則可以將JSON格式的字串解析為Python物件。

import json
thing = [{"hello": 1, "world": 2}, None, True]
json_str = json.dumps(thing)
print(json_str)  # 輸出:[{"hello": 1, "world": 2}, null, true]
loaded_thing = json.loads(json_str)
print(loaded_thing)  # 輸出:[{'hello': 1, 'world': 2}, None, True]

詳細解說:

  1. thing:一個包含字典、None和True的Python列表。
  2. json.dumps(thing):將Python物件轉換為JSON格式的字串。
  3. json.loads(json_str):將JSON格式的字串解析回Python物件。

JSON美化輸出

使用json.dumps()函式的額外引數,可以美化JSON輸出的格式,使其更易讀。

print(json.dumps(loaded_thing, indent=4, sort_keys=True))

詳細解說:

  1. indent=4:設定縮排為4個空格,使輸出更易讀。
  2. sort_keys=True:按鍵排序輸出,使輸出更有序。

HTTPX:自動化網頁API的強大工具

許多系統都提供根據網頁的API,而httpx函式庫正是為了自動化這些API而設計的。它不僅易於使用,還具備許多強大的功能。

為何選擇HTTPX?

  • httpx不支援Python 2,如果您仍在使用Python 2,請考慮升級至較新的版本,因為Python 2已經不再接收安全性更新。
  • 相較於Python標準函式庫中的HTTP客戶端設施,使用httpx幾乎總是更好的選擇。它支援彈性的身份驗證、內部序列化和反序列化JSON,並支援同步和非同步操作。
  • httpx與流行的requests函式庫高度相容。除非您使用了requests中的特殊功能,如特殊的憑證驗證,否則將使用requests的程式碼轉換為使用httpx只需要更改匯入陳述式。

使用客戶端

httpx中,建議使用明確的客戶端。雖然可以直接使用httpx.get/httpx.post等函式,但這實際上是在使用全域的客戶端物件。

import httpx

# 不建議這樣使用
response = httpx.get('https://example.com')

# 建議使用明確的客戶端
client = httpx.Client()
response = client.get('https://example.com')

為什麼要使用明確的客戶端?

  1. 避免全域可變分享狀態:使用全域客戶端物件可能會導致難以診斷的錯誤,例如當連線到使用cookie的網站時,不同的部分程式碼可能會覆寫彼此的cookie。
  2. 便於單元測試:使用明確的客戶端可以更容易地進行模擬,從而簡化單元測試。
  3. 存取更多功能:某些功能僅在使用明確的客戶端物件時才可存取,例如新增追蹤標頭或自定義使用者代理。

HTTPX的強大功能

  • JSON序列化和反序列化httpx可以自動處理JSON資料的序列化和反序列化。
  • 同步和非同步操作httpx支援同步和非同步操作,可以根據您的需求選擇合適的操作方式。
  • 彈性身份驗證httpx提供了彈性的身份驗證機制,可以輕鬆處理不同的身份驗證需求。

程式碼範例

import httpx

def fetch_data(url):
    with httpx.Client() as client:
        response = client.get(url)
        response.raise_for_status()  # 如果HTTP請求傳回了不成功的狀態碼,則引發HTTPError
        return response.json()

#### 內容解密:
1. 我們首先匯入了`httpx`模組這是Python中用於處理HTTP請求的現代函式庫
2.  `fetch_data`函式接受一個URL作為引數並使用`httpx.Client()`建立一個HTTP客戶端
3.  使用`client.get(url)`傳送GET請求到指定的URL
4.  `response.raise_for_status()`用於檢查HTTP請求是否成功如果傳回的狀態碼表示請求失敗4xx或5xx),則會引發`HTTPError`。
5.  如果請求成功則使用`response.json()`解析回應內容為JSON格式並將其傳回

data = fetch_data('https://api.example.com/data')
print(data)

使用顯式客戶端物件的重要性

在撰寫預期長期維護的程式碼時,使用顯式客戶端物件(Client object)是更好的選擇。這樣做的原因在於,將客戶端物件的初始化與主要邏輯分離,能夠使程式碼更具彈性與可維護性。

為什麼要使用顯式客戶端物件?

使用 httpx.Client() 建立客戶端物件後,所有與 HTTP 請求相關的操作都應透過該物件進行。客戶端物件提供了 .get().put().post().patch().options() 等方法,涵蓋了常見的 HTTP 請求操作。

客戶端物件作為上下文管理器

客戶端物件可以作為上下文管理器使用,這樣能夠確保在上下文結束時,所有待處理的連線都會被清理。這對於有嚴格使用限制的伺服器尤其重要。

with httpx.Client() as c:
    c.get(...)

避免依賴 Python 的參照計數

依賴 Python 的參照計數來關閉連線是危險的。Python 語言並不保證這種行為,而且在某些實作(如 PyPy)中,這種行為並不成立。此外,一些小細節(如客戶端被捕捉為堆積疊追蹤中的區域性變數)就可能阻止參照計數機制正常運作,導致連線長時間未被關閉。

設定客戶端物件的引數

客戶端物件支援多個建構子引數,用於設定請求的預設行為。其中最常用的引數包括:

  • auth=:用於設定驗證資訊。
  • headers=:用於設定預設的請求標頭。

設定 User-Agent

在測試 Web API 時,在 User-Agent 中加入識別字串是非常有用的。這樣可以透過伺服器日誌區分哪些請求來自測試程式碼。

client = httpx.Client(
    headers={'User-Agent': 'Python/MySoftware ' + __version__}
)

客戶端物件內部維護了一個 CookieJar,可以透過 Client(cookies=cookie_jar) 建構子引數進行設定。這對於需要持久化 Cookie 的場景非常有用,例如實作可重新啟動的 HTTP 會話。

此外,客戶端物件也支援設定客戶端憑證,用於需要憑證驗證的場景。

REST 簡介

REST(Representational State Transfer)是一種鬆散且廣泛應用的網路資源表示標準。它常用於將資料函式庫結構直接對映到 Web 上,此時常被稱為 CRUD 模型(Create, Retrieve, Update, Delete)。

RESTful 操作與 HTTP 方法

  • Create:通常使用 POST 方法實作。
  • Retrieve:使用 GET 方法實作,大多數情況下是唯讀且安全的。
  • Update:可以使用 PUT(完整更新)或 PATCH(部分更新)方法實作。
  • Delete:使用 DELETE 方法實作。

使用 httpx 進行 RESTful 操作

httpx 函式庫對 JSON 有特殊的支援,可以方便地進行 JSON 資料的編碼與解碼。

>>> resp = c.put("https://httpbin.org/put", json=dict(hello=5, world=2))
>>> resp.json()['json']
{'hello': 5, 'world': 2}

REST API 的典型用法

一個良好的 REST API 會在回應中包含資源識別符(URL),這些 URL 可以用於進一步的操作。

>>> res = c.get("https://api.github.com/repos/python/cpython/pulls")
>>> commits_url = res.json()[0]['commits_url']
>>> commits = c.get(commits_url).json()
>>> commits[0]['commit']['message'][:40]
'bpo-46104: Fix example broken by GH-3014'