可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Introduction
The string module has a Template class, that lets you make substitutions in a string using a mapping object, for instance:
>>> string.Template('var is $var').substitute({'var': 1}) 'var is 1'
The substitute method may raise a KeyError exception, if an attempt is made to substitute an element that is missing from the mapping, for instance
>>> string.Template('var is $var and foo is $foo').substitute({'var': 1}) KeyError: 'foo'
or may raise a ValueError, if the template string is invalid, e.g. it contains a $ character followed by a space:
>>> string.Template('$ var is $var').substitute({'var': 1}) ValueError: Invalid placeholder in string: line 1, col 1
The Problem
Given a template string and a mapping, I want to determine whether all place-holders in the template would be substituted. For this, I would try to make the substitution and catch any KeyError exception:
def check_substitution(template, mapping): try: string.Template(template).substitute(mapping) except KeyError: return False except ValueError: pass return True
But this doesn't work, because if the template is invalid and a ValueError is raised, subsequent KeyErrors aren't caught:
>>> check_substitution('var is $var and foo is $foo', {'var': 1}) False >>> check_substitution('$ var is $var and foo is $foo', {'var': 1}) True
but I don't care about ValueErrors. So, what would be the right approach to this problem?
回答1:
The docs say that you can replace the pattern as long as it contains all necessary named groups:
import re from string import Template class TemplateIgnoreInvalid(Template): # override pattern to make sure `invalid` never matches pattern = r""" %(delim)s(?: (?P%(delim)s) | # Escape sequence of two delimiters (?P%(id)s) | # delimiter and a Python identifier {(?P%(id)s)} | # delimiter and a braced identifier (?P^$) # 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
Tests
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)
It works for all invalid occurences of the delimiter ($).
The examples show that ignoring invalid patterns conceals simple typos in the template so it is not a good API.
回答2:
This is a Quick Fix (Using recursion):
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
I Know its take a longer time if there is more than One Space between $ and var , so you may improve it by using Regular Expression.
EDIT
escaping $ into $$ makes more sense [ Thanks @Pedro ] so you can catch ValueError by this statement:
return check_substitution(tem.replace('$ ', '$$ '), m) #escaping $ by $$
回答3:
Python will not do string substitution over multiple lines
If you have this string
criterion = """ {order} """ criterion.format(dict(order="1",code="Hello")
results in:
KeyError: 'order'
A solution is to use the string.Template module
from string import Template criterion = """ $order """ Template(criterion).substitute(dict(order="1",code="hello")
NOTE: you have to prefix the keywords with a $ not wrap them in {}
output is:
1
Full docs are: https://docs.python.org/2/library/string.html#template-strings