自定义模板变量替换器的实现方案
在数据模板开发中,经常需要预留动态参数位,例如$user或{{user}}等占位符形式,以便后续注入实际数据。除了Python内置的格式化手段外,有时需要更灵活的替换机制,特别是处理嵌套结构数据时。
Python原生格式化方式回顾
常见的字符串插值方法包括:
# 百分号风格
'用户: %s, 积分: %d' % ('Alice', 150)
# 命名参数风格
'用户: %(user)s, 积分: %(points)d' % {'user': 'Alice', 'points': 150}
# 花括号风格
'用户: {}, 积分: {}'.format('Alice', 150)
'用户: {user}, 积分: {points}'.format(user='Alice', points=150)
# Template类风格
from string import Template
Template('用户: $user, 积分: $points').substitute(user='Alice', points=150)
专业模板引擎如Jinja2则提供更强大的能力:
from jinja2 import Template
Template('用户: {{ user }}, 状态: {{ "VIP" if vip else "普通" }}').render(user='Alice', vip=True)
特殊场景需求
当处理YAML配置或复杂嵌套结构时,%和{}可能与语法冲突,因此选择$作为定界符更为安全。此外,需要支持以下特性:
- 位置参数引用(如
$1表示第一个参数) - 关键字参数引用(如
$token) - 深度遍历字典、列表、元组等嵌套结构
- 未匹配时保持原样,不抛出异常
- 支持跨行文本处理
核心实现思路
利用re.sub的回调函数机制,对每个匹配到的占位符执行自定义解析逻辑:
re.sub(pattern, replacement_callback, source_text, flags=re.MULTILINE)
完整实现代码
import re
import json
def inject_variables(template, *positional, marker="$", **named):
"""
递归替换模板中的变量占位符
Args:
template: 待处理的字符串、字典、列表或元组
*positional: 位置参数,通过$1, $2等引用
marker: 定界符,默认为$
**named: 命名参数,通过$var引用
"""
regex = r'\{}(?P<key>[\w_]+)'.format(marker)
def resolve(match):
key = match.group('key')
# 数字索引:从位置参数取值
if key.isdigit():
idx = int(key) - 1
if 0 <= idx < len(positional):
return str(positional[idx])
return "{}{}".format(marker, key)
# 名称索引:从关键字参数取值
return str(named.get(key, "{}{}".format(marker, key)))
# 字符串类型直接处理
if isinstance(template, str):
return re.sub(regex, resolve, template, flags=re.M)
# 序列类型:先序列化为JSON字符串,统一替换后再还原
if isinstance(template, (dict, list, tuple)):
serialized = json.dumps(template, ensure_ascii=False)
replaced = re.sub(regex, resolve, serialized, flags=re.M)
parsed = json.loads(replaced)
# 元组需要特殊处理
if isinstance(template, tuple):
return tuple(parsed)
return parsed
return template
# 实际应用演示
if __name__ == '__main__':
sample = [
'设备: $2 版本: $3\n备注: $note',
'$1',
{"message": "$note", "items": ["$2", "$3"]}
]
result = inject_variables(
sample,
'Router-A', 'Cisco-9000', '17.3.2',
note="生产环境部署"
)
print(result)
执行后输出:
['设备: Cisco-9000 版本: 17.3.2\n备注: 生产环境部署', 'Router-A', {'message': '生产环境部署', 'items': ['Cisco-9000', '17.3.2']}]
设计要点说明
序列化策略:对于容器类型,采用JSON作为中间表示,既能保留结构信息,又能在字符串层面统一完成替换,避免递归遍历的复杂性。
容错机制:当参数不足或键名不存在时,返回原始占位符而非报错,确保模板在部分数据缺失时仍可正常渲染。
类型保持:通过判断原始类型,确保元组输入得到元组输出,列表输入得到列表输出,维持数据结构的完整性。