本文 AI 產出,尚未審核

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 僅支援 字串 替換,無法直接執行表達式或調用函式,這正是它相較於 evalexec 更安全的原因。

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 並覆寫 delimiteridpattern 來改變語法。

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 物件保存)

最佳實踐

  1. 模板與資料分離:將模板存於 .txt.tmpl.conf 檔案,程式只負責載入與填值。
  2. 使用 safe_substitute:在不確定所有佔位符都有值的情況下,避免例外中斷。
  3. 限制可用變數:若模板由外部提供,先建立白名單,只允許特定鍵進行替換。
  4. 快取 Template 物件:對於頻繁使用的模板,先建立一次 Template,重複呼叫 substitute,減少解析成本。
  5. 避免在模板中使用敏感資訊:不要把密碼、金鑰等直接寫入模板,應在程式碼中加密或使用環境變數。

實際應用場景

  1. 自動化郵件與簡訊

    • 客戶關懷、訂單確認、密碼重設等,都可以將文字內容寫成模板,根據不同使用者資料快速產生訊息。
  2. 產生設定檔或腳本

    • DevOps 常需要根據環境變數產生 docker-compose.ymlnginx.conf、Shell 腳本等,使用 Template 可以保證佔位符一致且易於維護。
  3. 報表與日誌

    • 將每日/每週報表的格式抽離,僅在程式中提供資料,讓報表樣式變更不需改動程式邏輯。
  4. 國際化(i18n)

    • 雖然 gettext 更常用於翻譯,但簡易的多語系文字也可利用 Template 把變數部分抽出,降低硬編碼的風險。
  5. 測試資料產生

    • 單元測試或端對端測試常需要大量相似但略有差異的字串(如 JSON 請求),使用模板能快速產出多樣化測試案例。

總結

string.Template 提供了一套 簡潔、可讀且安全 的字串置換機制,特別適合:

  • 需要 外部編輯 的文字模板(如郵件、設定檔)
  • 安全性 高於表達式求值的情境
  • 想要 快速產生 多筆相似文字的場景

掌握 Template 的基本語法、substitutesafe_substitute 的差異、以及自訂佔位符的技巧,能讓你在 Python 開發中更靈活地處理文字輸出。配合 分離模板、快取物件、限制變數 等最佳實踐,你的程式碼不僅易於維護,也更具韌性。快把這些範例搬到自己的專案裡試試,體驗「字串模板」帶來的開發效率提升吧!