Sustituciones de cadenas usando plantillas en Python

Introducción

El módulo de cadena tiene una clase de Plantilla, que le permite hacer sustituciones en una cadena usando un objeto de mapeo, por ejemplo:

>>> string.Template('var is $var').substitute({'var': 1}) 'var is 1' 

El método de sustitución puede generar una excepción KeyError, si se intenta sustituir un elemento que falta en la asignación, por ejemplo

 >>> string.Template('var is $var and foo is $foo').substitute({'var': 1}) KeyError: 'foo' 

o puede generar un ValueError, si la cadena de la plantilla no es válida, por ejemplo, contiene un carácter $ seguido de un espacio:

 >>> string.Template('$ var is $var').substitute({'var': 1}) ValueError: Invalid placeholder in string: line 1, col 1 

El problema

Dada una cadena de plantilla y una asignación, quiero determinar si se sustituirán todos los marcadores de posición en la plantilla. Para esto, intentaría realizar la sustitución y detectar cualquier excepción KeyError:

 def check_substitution(template, mapping): try: string.Template(template).substitute(mapping) except KeyError: return False except ValueError: pass return True 

Pero esto no funciona, porque si la plantilla no es válida y se genera un ValueError, los KeyErrors subsiguientes no se capturan:

 >>> check_substitution('var is $var and foo is $foo', {'var': 1}) False >>> check_substitution('$ var is $var and foo is $foo', {'var': 1}) True 

pero no me importa ValueErrors. Entonces, ¿cuál sería el enfoque correcto para este problema?

Los documentos dicen que puede reemplazar el patrón siempre que contenga todos los grupos nombrados necesarios:

 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 

Pruebas

 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) 

Funciona para todas las apariciones inválidas del delimitador ( $ ).

Los ejemplos muestran que ignorar patrones no válidos oculta errores tipográficos simples en la plantilla, por lo que no es una buena API.

Esta es una solución rápida (mediante recursión):

 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 

Sé que lleva más tiempo si hay más de un espacio entre $ y var , por lo que puede mejorarlo utilizando Expresión regular.

EDITAR

escapar de $ a $$ tiene más sentido [Gracias @Pedro] para que pueda capturar a ValueError con esta statement:

 return check_substitution(tem.replace('$ ', '$$ '), m) #escaping $ by $$ 

Python no realizará la sustitución de cadenas en varias líneas

Si tienes esta cadena

 criterion = """  {order}   """ criterion.format(dict(order="1",code="Hello") 

resultados en:

 KeyError: 'order' 

Una solución es utilizar el módulo string.Template.

 from string import Template criterion = """  $order   """ Template(criterion).substitute(dict(order="1",code="hello") 

NOTA: tiene que prefijar las palabras clave con un $ no envolverlas en {}

la salida es:

   1   

Los documentos completos son: https://docs.python.org/2/library/string.html#template-strings