Python 資料結構 - 字串(str)
主題:字串模板(Template)
簡介
在日常開發中,我們常需要把變數值插入到固定格式的文字裡,例如產生電子郵件內容、動態產生 SQL 語句或是產出日誌訊息。雖然 Python 的 f‑string、str.format() 已經相當普及,但在 安全性 與 可讀性 需求較高的情境下,string.Template 仍是一個值得掌握的工具。
Template 以 簡潔的佔位語法(${var})取代了繁雜的格式化字串,且內建 字串安全過濾(避免意外執行程式碼),非常適合 使用者提供的模板 或 外部檔案(如 .txt、.conf)中動態填入資料。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你快速上手 Template,並了解它在實務上的應用。
核心概念
1. 什麼是 string.Template
string.Template 是 Python 標準庫 string 模組提供的類別,用來 以字典方式替換字串中的變數。其語法規則相當簡單:
| 佔位符 | 說明 |
|---|---|
${var} |
取代為字典中 var 的值 |
$var |
若變數名稱僅由字母、數字、底線組成,可省略大括號 |
$$ |
產生單一 $ 符號(逃脫) |
注意:
Template僅支援 字串 替換,無法直接執行表達式或調用函式,這正是它相較於eval、exec更安全的原因。
2. 建立與使用 Template
from string import Template
# 建立模板字串
tmpl = Template('Hello, ${name}! 今天是 ${day}。')
# 使用字典提供資料
result = tmpl.substitute(name='Alice', day='星期五')
print(result) # => Hello, Alice! 今天是 星期五。
substitute():若佔位變數缺失,會拋出KeyError。safe_substitute():缺失變數時會保留原佔位文字,避免例外。
3. 從檔案載入模板
在實務上,我們常把模板寫在外部檔案,讓非程式人員也能編輯內容。
from string import Template
with open('email_template.txt', encoding='utf-8') as f:
tmpl = Template(f.read())
data = {'user': '王小明', 'date': '2025/12/01', 'link': 'https://example.com'}
email_body = tmpl.safe_substitute(data)
print(email_body)
email_template.txt內容範例親愛的 ${user} 您好, 您的帳號於 ${date} 進行了登入,若非本人操作,請立即點擊以下連結: ${link}
4. 自訂佔位符規則
若預設的 ${var} 不符合需求,可以透過繼承 Template 並覆寫 delimiter、idpattern 來改變語法。
from string import Template
import re
class MyTemplate(Template):
delimiter = '%' # 使用 % 作為前綴
idpattern = r'[a-zA-Z_]+' # 只允許字母與底線
tmpl = MyTemplate('使用者 %username 於 %date 登入。')
print(tmpl.substitute(username='Bob', date='2025-11-20'))
# => 使用者 Bob 於 2025-11-20 登入。
5. 多層次資料的處理
Template 本身不支援點號(.)或索引語法,但我們可以先 預處理字典,將巢狀資料平鋪。
from string import Template
data = {
'user': {'first': '阿美', 'last': '陳'},
'order': {'id': 12345, 'total': 2990}
}
# 平鋪字典
flat = {
'first_name': data['user']['first'],
'last_name': data['user']['last'],
'order_id': data['order']['id'],
'order_total': data['order']['total']
}
tmpl = Template('親愛的 ${first_name} ${last_name},您的訂單 ${order_id} 金額為 ${order_total} 元。')
print(tmpl.substitute(flat))
程式碼範例
以下提供 5 個常見且實用的範例,每個範例皆附上說明與可能的變化。
範例 1:簡易的命令列說明文字
from string import Template
usage_tpl = Template("""\
用法: $prog [選項] <檔案>
選項:
-h, --help 顯示此說明
-v, --version 顯示版本資訊
""")
print(usage_tpl.substitute(prog='mytool'))
說明:利用 $prog 動態插入程式名稱,讓說明文字可重複使用於不同工具。
範例 2:產生 HTML 電子報
from string import Template
html_tpl = Template("""\
<html>
<head><title>${title}</title></head>
<body>
<h1>${heading}</h1>
<p>親愛的 ${name},感謝您的訂閱!</p>
<a href="${link}">點此前往</a>
</body>
</html>
""")
data = {
'title': '每月電子報',
'heading': '歡迎閱讀',
'name': '陳大文',
'link': 'https://example.com/newsletter'
}
print(html_tpl.substitute(data))
說明:將 HTML 內容抽離成模板,讓後端只負責填入資料,前端設計師可直接編輯 .html 檔。
範例 3:安全的日誌訊息(避免 KeyError)
from string import Template
import logging
log_tpl = Template('使用者 ${user} 在 ${time} 執行 ${action},結果為 ${result}')
def log_action(**kwargs):
msg = log_tpl.safe_substitute(kwargs) # 若缺少欄位不會拋例外
logging.info(msg)
log_action(user='alice', time='10:23', action='登入')
# 輸出: 使用者 alice 在 10:23 執行 登入,結果為 ${result}
說明:使用 safe_substitute 防止缺少欄位時程式中斷,適合不確定所有資訊都齊全的日誌情境。
範例 4:自訂分隔符與驗證變數名稱
from string import Template
class EnvTemplate(Template):
delimiter = '$' # 繼承預設
idpattern = r'[A-Z][A-Z0-9_]*' # 必須是大寫字母開頭
env_tpl = EnvTemplate('PATH=$PATH\nHOME=$HOME\nUSER=$USER')
print(env_tpl.substitute(PATH='/usr/bin', HOME='/home/alice', USER='alice'))
說明:限制變數只能是全大寫,適合產生環境設定檔(.env)時避免誤植小寫。
範例 5:從資料庫查詢結果產生報表
from string import Template
import sqlite3
report_tpl = Template("""\
報表日期:${date}
---------------------------------
${rows}
總筆數:${count}
""")
def fetch_data():
conn = sqlite3.connect('example.db')
cur = conn.cursor()
cur.execute('SELECT name, score FROM scores')
rows = cur.fetchall()
conn.close()
return rows
rows = fetch_data()
row_str = '\n'.join([f'{i+1}. {name}: {score}' for i, (name, score) in enumerate(rows)])
data = {
'date': '2025-11-20',
'rows': row_str,
'count': len(rows)
}
print(report_tpl.substitute(data))
說明:先把查詢結果轉成字串,再套用 Template 生成完整報表,保持程式碼結構清晰。
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 解決方式 |
|---|---|---|
使用 $ 但未逃脫 |
產生 KeyError 或意外替換 |
需要插入實際 $ 時使用 $$(或 safe_substitute) |
變數名稱不符合 idpattern |
ValueError: Invalid placeholder |
確認變數名稱只含允許的字元,或自訂 idpattern |
| 直接使用使用者輸入作為模板 | 可能導致 模板注入(雖然不會執行程式碼,但會改變輸出) | 僅允許可信來源的模板,或使用 safe_substitute 並限制可用變數 |
| 忘記傳入全部必需的鍵 | KeyError 中斷程式 |
使用 safe_substitute 或先檢查字典鍵集合 |
| 大量字串拼接導致效能問題 | 產生不必要的記憶體開銷 | 若需要大量重複渲染,考慮使用 str.join 或 模板快取(將 Template 物件保存) |
最佳實踐
- 模板與資料分離:將模板存於
.txt、.tmpl或.conf檔案,程式只負責載入與填值。 - 使用
safe_substitute:在不確定所有佔位符都有值的情況下,避免例外中斷。 - 限制可用變數:若模板由外部提供,先建立白名單,只允許特定鍵進行替換。
- 快取
Template物件:對於頻繁使用的模板,先建立一次Template,重複呼叫substitute,減少解析成本。 - 避免在模板中使用敏感資訊:不要把密碼、金鑰等直接寫入模板,應在程式碼中加密或使用環境變數。
實際應用場景
自動化郵件與簡訊
- 客戶關懷、訂單確認、密碼重設等,都可以將文字內容寫成模板,根據不同使用者資料快速產生訊息。
產生設定檔或腳本
- DevOps 常需要根據環境變數產生
docker-compose.yml、nginx.conf、Shell 腳本等,使用Template可以保證佔位符一致且易於維護。
- DevOps 常需要根據環境變數產生
報表與日誌
- 將每日/每週報表的格式抽離,僅在程式中提供資料,讓報表樣式變更不需改動程式邏輯。
國際化(i18n)
- 雖然
gettext更常用於翻譯,但簡易的多語系文字也可利用Template把變數部分抽出,降低硬編碼的風險。
- 雖然
測試資料產生
- 單元測試或端對端測試常需要大量相似但略有差異的字串(如 JSON 請求),使用模板能快速產出多樣化測試案例。
總結
string.Template 提供了一套 簡潔、可讀且安全 的字串置換機制,特別適合:
- 需要 外部編輯 的文字模板(如郵件、設定檔)
- 安全性 高於表達式求值的情境
- 想要 快速產生 多筆相似文字的場景
掌握 Template 的基本語法、substitute 與 safe_substitute 的差異、以及自訂佔位符的技巧,能讓你在 Python 開發中更靈活地處理文字輸出。配合 分離模板、快取物件、限制變數 等最佳實踐,你的程式碼不僅易於維護,也更具韌性。快把這些範例搬到自己的專案裡試試,體驗「字串模板」帶來的開發效率提升吧!