String substitutions using templates in Python

匿名 (未验证) 提交于 2019-12-03 01:49:02

问题:

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



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!