Python str.format() 支援映射解包語法 **mapping,配合 locals()、vars() 或自訂字典,能實現極度簡潔的變數插值,避免重複變數名稱。本文詳細說明映射解包語法、locals() 應用、作用域陷阱、安全性考量與最佳實務。
name = "jack"
age = 24
# 傳統格式化:重複變數名
s1 = "name is {name} and age is {age}".format(name=name, age=age)
# 映射解包:自動對應同名鍵
s2 = "name is {name} and age is {age}".format(**locals())
# 等價於 .format(**{'name': 'jack', 'age': 24})
print(s1) # name is jack and age is 24
print(s2) # name is jack and age is 24
def demo_locals():
x = 10
y = 20
print(locals()) # {'x': 10, 'y': 20, ...}
demo_locals()
# 全域 locals() 等同 globals()
print(locals() is globals()) # True(模組層級)
def log_call(func_name, **params):
"""自動記錄函式呼叫參數"""
template = "{func_name}({params})"
param_str = ", ".join(f"{k}={v!r}" for k, v in params.items())
print(template.format(func_name=func_name, params=param_str))
# 使用:將參數打包為字典
args = {'user_id': 123, 'action': 'login', 'ip': '192.168.1.1'}
log_call("auth", **args)
# auth(user_id=123, action='login', ip='192.168.1.1')
# 設定模板(可外部載入)
CONFIG_TEMPLATE = """
[database]
host = {db_host}
port = {db_port}
user = {db_user}
password = {db_pass}
"""
# 環境變數或設定來源
config = {
'db_host': 'localhost',
'db_port': 5432,
'db_user': 'admin',
'db_pass': 'secret123'
}
rendered = CONFIG_TEMPLATE.format(**config)
print(rendered)
class User:
def __init__(self, name, email, role):
self.name = name
self.email = email
self.role = role
user = User("Alice", "alice@example.com", "admin")
# vars(obj) 等同 obj.__dict__
template = "User: {name}, Email: {email}, Role: {role}"
print(template.format(**vars(user)))
# User: Alice, Email: alice@example.com, Role: admin
base = {'app': 'MyApp', 'version': '1.0'}
# 顯式參數覆蓋映射值
s = "{app} v{version} - {env}".format(**base, env="production")
print(s) # MyApp v1.0 - production
# 衝突時顯式參數優先
s = "{app} v{version}".format(**{'app': 'Old', 'version': '0.9'}, app="New", version="2.0")
print(s) # New v2.0
def format_user(name, age):
# locals() = {'name': name, 'age': age, ...}
# 若模板只需 name,age 也會被傳入(無害但多餘)
return "User: {name}".format(**locals())
# 建議:顯式建構字典
def format_user_v2(name, age):
return "User: {name}".format(name=name)
# 或
def format_user_v3(name, age):
return "User: {name}".format(**{'name': name})
for i in range(3):
x = i * 10
# 迴圈結束後,i, x 仍在 locals() 中
print(locals()) # 包含 'i': 2, 'x': 20
locals() 建立字典副本,頻繁呼叫有開銷。熱路徑請用顯式字典。
# 危險:使用者控制格式化字串
user_input = "{__import__('os').system('rm -rf /')}"
# template.format(**locals()) 可能執行惡意代碼(視 Python 版本)
# 安全實務:
# 1. 格式化字串不可來自使用者輸入
# 2. 使用 string.Template(較安全,功能受限)
from string import Template
t = Template("Hello $name")
print(t.substitute(name="Alice")) # 僅支援 $var 或 ${var}
# 3. 驗證格式化字串鍵名
ALLOWED_KEYS = {'name', 'age', 'email'}
def safe_format(template, data):
used_keys = set(re.findall(r'\{(\w+)\}', template))
if not used_keys.issubset(ALLOWED_KEYS):
raise ValueError("模板包含不允許的鍵")
return template.format(**data)
# format_map 不複製字典,直接使用(支援 __missing__)
class SafeDict(dict):
def __missing__(self, key):
return "{" + key + "}" # 缺鍵保留原樣
data = {'name': 'Alice'}
print("Hello {name}, age {age}".format_map(SafeDict(data)))
# Hello Alice, age {age}(不拋出 KeyError)
name = "jack"
age = 24
# 直接使用變數,無需解包
print(f"name is {name} and age is {age}")
# 複雜表達式
print(f"Next year: {age + 1}")
print(f"Name upper: {name.upper()}")
# 格式化規格
import math
print(f"Pi: {math.pi:.3f}")
**locals() 映射解包是快速原型、除錯輸出、模板渲染的便利技巧,**但生產代碼建議顯式建構字典**,避免作用域洩漏、效能開銷、安全性風險。掌握 vars(obj)、format_map()、SafeDict 進階模式,能靈活處理物件序列化、設定渲染、日誌格式化等場景。現代 Python 3.6+ 首選 f-string,語法最簡潔、效能最佳、功能最強。