在處理表格輸出、日誌格式化、程式碼產生等場景時,常需將字串中的製表符(\t)轉換為固定寬度的空格以達成對齊。Python 內建 str.expandtabs() 方法專為此設計,支援自訂 tab 寬度,本文完整解析其行為、參數與實戰技巧。
str.expandtabs(tabsize=8) -> str
\t 替換為適量空格,使下一字元落在下一個 tab stop(tabsize 的倍數位置)。tab stop 位置為 0, tabsize, 2*tabsize, 3*tabsize, ...。遇到 \t 時,補齊空格直到下一個 stop。
# tabsize=8 (預設)
"a\tb" -> "a b" # a 在 0,下一 stop 8,補 7 空格
"abc\tde" -> "abc de" # abc 佔 0-2,下一 stop 8,補 5 空格
"12345678\tX" -> "12345678 X" # 已在 stop 8,補 1 空格到 stop 16
s = "Name\tAge\tCity"
print("預設(8):", repr(s.expandtabs()))
# 'Name Age City'
print("tabsize=4:", repr(s.expandtabs(4)))
# 'Name Age City'
print("tabsize=12:", repr(s.expandtabs(12)))
# 'Name Age City'
data = [
("Alice", "30", "Taipei"),
("Bob", "25", "Tokyo"),
("Charlie", "35", "New York")
]
# 計算每欄最大寬度(含標頭)
headers = ("Name", "Age", "City")
col_widths = [max(len(str(row[i])) for row in [headers] + data) for i in range(3)]
# 產生格式化字串:用 \t 分隔,再 expandtabs 到最大寬度+2
# 注意:expandtabs 是「跳到下一 stop」,非固定寬度,需配合計算
def format_row(row, widths):
parts = []
for i, val in enumerate(row):
val = str(val)
# 手動左對齊填充到指定寬度,再加 \t 讓 expandtabs 只補 1 空格
parts.append(val.ljust(widths[i]) + "\t")
return "".join(parts).expandtabs(1) # tabsize=1 相當於不再展開,只保留手動填充
for row in [headers] + data:
print(format_row(row, col_widths))
輸出:
Name Age City Alice 30 Taipei Bob 25 Tokyo Charlie 35 New York
expandtabs vs str.format vs f-string| 方法 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| expandtabs | 舊代碼遷移、簡單 tab 分隔資料 | 一行搞定、無需計算寬度 | 難以精確控制每欄寬度、中文字元寬度不等 |
| str.format / f-string | 現代代碼、精確對齊、中文支援 | 完全控制、支援對齊/填充/精度 | 需預先知道寬度 |
| textwrap / tabulate | 複雜表格、自動換行、邊框 | 功能豐富、專業輸出 | 需額外依賴 |
expandtabs 按**字元數**計算寬度,但中文字元在終端機通常佔 2 格寬度(East Asian Width)。直接使用會導致對齊錯位:
s = "姓名\t年齡\t城市"
print(s.expandtabs(8))
# 輸出視覺上會錯位,因 "姓名" 2 字元但佔 4 格寬
解決方案:使用 wcwidth 套件計算顯示寬度,或改用 tabulate / rich 等支援 CJK 的庫。
s.expandtabs():預設 8 字元,快速檢視 tab 分隔資料。s.expandtabs(4):程式碼縮排轉空格(PEP 8 建議 4 空格)。s.expandtabs(0):移除所有 tab(替換為空字串)。'\t'.join(fields).expandtabs(width):快速產生粗略對齊表格。
import sys, os
def convert_file(path, tabsize=4, backup=True):
if backup:
os.rename(path, path + ".bak")
with open(path + ".bak", 'r', encoding='utf-8') as f:
content = f.read()
with open(path, 'w', encoding='utf-8') as f:
f.write(content.expandtabs(tabsize))
print(f"Converted: {path}")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python tab2space.py file1.py [file2.py ...]")
sys.exit(1)
for f in sys.argv[1:]:
convert_file(f)
掌握 expandtabs() 的 tab stop 機制與限制,能在快速原型、舊代碼整理、簡單表格輸出場合發揮最大效用;正式報表建議改用 f-string 或專業表格庫。