Подстановки строк с использованием шаблонов в Python
введение
модуль string имеет класс шаблона, который позволяет выполнять подстановки в строке с помощью объекта сопоставления, например:
>>> string.Template('var is $var').substitute({'var': 1})
'var is 1'
метод substitute может вызвать исключение KeyError, если сделана попытка заменить элемент, который отсутствует в сопоставлении, например
>>> string.Template('var is $var and foo is $foo').substitute({'var': 1})
KeyError: 'foo'
или может вызвать ValueError, если строка шаблона недопустима, например, она содержит $
символ, за которым следует пространство:
>>> string.Template('$ var is $var').substitute({'var': 1})
ValueError: Invalid placeholder in string: line 1, col 1
Проблема
учитывая строку шаблона и сопоставление, я хочу определить, будут ли заменены все держатели места в шаблоне. Для этого я бы попытался сделать замену и поймать любое исключение KeyError:
def check_substitution(template, mapping):
try:
string.Template(template).substitute(mapping)
except KeyError:
return False
except ValueError:
pass
return True
но это не работает, потому что если шаблон недействителен и ValueError возникает, последующие KeyErrors не пойманы:
>>> check_substitution('var is $var and foo is $foo', {'var': 1})
False
>>> check_substitution('$ var is $var and foo is $foo', {'var': 1})
True
но я не важно о ValueErrors. Итак, каков будет правильный подход к этой проблеме?
3 ответов
врачи говорят что вы можете заменить шаблон, если он содержит все необходимые именованные группы:
import re
from string import Template
class TemplateIgnoreInvalid(Template):
# override pattern to make sure `invalid` never matches
pattern = r"""
%(delim)s(?:
(?P<escaped>%(delim)s) | # Escape sequence of two delimiters
(?P<named>%(id)s) | # delimiter and a Python identifier
{(?P<braced>%(id)s)} | # delimiter and a braced identifier
(?P<invalid>^$) # never matches (the regex is not multilined)
)
""" % dict(delim=re.escape(Template.delimiter), id=Template.idpattern)
def check_substitution(template, **mapping):
try:
TemplateIgnoreInvalid(template).substitute(mapping)
except KeyError:
return False
else:
return True
тесты
f = check_substitution
assert f('var is $var', var=1)
assert f('$ var is $var', var=1)
assert f('var is $var and foo is $foo', var=1, foo=2)
assert not f('var is $var and foo is $foo', var=1)
assert f('$ var is $var and foo is $foo', var=1, foo=2)
assert not f('$ var is $var and foo is $foo', var=1)
# support all invalid patterns
assert f('var is $var and foo is ${foo', var=1)
assert f('var is $var and foo is ${foo', var=1, foo=2) #NOTE: problematic API
assert f('var is $var and foo is ${foo and ${baz}', var=1, baz=3)
assert not f('var is $var and foo is ${foo and ${baz}', var=1)
он работает для всех недопустимых случаев разделителя ($
).
примеры показывают, что игнорирование недопустимых шаблонов скрывает простые опечатки в шаблоне, поэтому это не хороший API.
это быстрое исправление (с помощью рекурсии):
def check_substitution(tem, m):
try:
string.Template(tem).substitute(m)
except KeyError:
return False
except ValueError:
return check_substitution(tem.replace('$ ', '$'), m) #strip spaces after $
return True
Я знаю, что это займет больше времени, если между $
и var
, поэтому вы можете улучшить его, используя регулярное выражение.
редактировать
побег $
на $$
имеет больше смысла [ спасибо @Pedro ], так что вы можете поймать ValueError
на это заявление:
return check_substitution(tem.replace('$ ', '$$ '), m) #escaping $ by $$
Python не будет выполнять подстановку строк по нескольким строкам
если у вас есть эта строка
criterion = """
<criteria>
<order>{order}</order>
<body><![CDATA[{code}]]></body>
</criteria>
"""
criterion.format(dict(order="1",code="Hello")
результаты:
KeyError: 'order'
решение заключается в использовании строку.Модуль шаблона
from string import Template
criterion = """
<criteria>
<order>$order</order>
<body><![CDATA[$code]]></body>
</criteria>
"""
Template(criterion).substitute(dict(order="1",code="hello")
Примечание: Вы должны префикс ключевых слов с $ не обернуть их в {}
вывод:
<criteria>
<order>1</order>
<body><![CDATA[hello]]></body>
</criteria>
полные документы:https://docs.python.org/2/library/string.html#template-strings