Introducci贸n
Contenido
Las plantillas de Python se utilizan para sustituir datos en cadenas. Con las plantillas, obtenemos una interfaz muy personalizable para la sustituci贸n de cadenas (o interpolaci贸n de cadenas).
Python ya ofrece muchas formas de sustituir cadenas, incluidas las f-Strings recientemente introducidas. Si bien es menos com煤n sustituir cadenas por plantillas, su poder radica en c贸mo podemos personalizar nuestras reglas de formato de cadenas.
En este art铆culo, formatearemos cadenas con la Template
clase de Python . Luego, veremos c贸mo podemos cambiar la forma en que nuestras plantillas pueden sustituir datos en cadenas.
Para comprender mejor estos temas, necesitar谩 algunos conocimientos b谩sicos sobre c贸mo trabajar con clases y expresiones regulares.
Comprender la clase de plantilla de Python
La Template
clase Python se agreg贸 al string
m贸dulo desde Python 2.4. Esta clase est谩 pensada para usarse como una alternativa a las opciones de sustituci贸n integradas (principalmente a %
) para crear plantillas complejas basadas en cadenas y para manejarlas de una manera f谩cil de usar.
La implementaci贸n de la clase utiliza expresiones regulares para coincidir con un patr贸n general de cadenas de plantilla v谩lidas . Una cadena de plantilla v谩lida, o marcador de posici贸n, consta de dos partes:
- El
$
s铆mbolo - Un identificador de Python v谩lido. Un identificador es cualquier secuencia de letras may煤sculas y min煤sculas de la A a la Z, guiones bajos (
_
) y d铆gitos del 0 al 9. Un identificador no puede comenzar con d铆gitos ni puede ser una palabra clave de Python.
En una cadena de plantilla, $name
y $age
se considerar铆an marcadores de posici贸n v谩lidos.
Para usar la Template
clase Python en nuestro c贸digo, necesitamos:
- Importar
Template
desde elstring
m贸dulo - Crea una cadena de plantilla v谩lida
- Crear una instancia
Template
usando la cadena de la plantilla como argumento - Realizar la sustituci贸n mediante un m茅todo de sustituci贸n
Aqu铆 hay un ejemplo b谩sico de c贸mo podemos usar la Template
clase Python en nuestro c贸digo:
>>> from string import Template
>>> temp_str="Hi $name, welcome to $site"
>>> temp_obj = Template(temp_str)
>>> temp_obj.substitute(name="John Doe", site="Pharos.sh.com")
'Hi John Doe, welcome to Pharos.sh.com'
Notamos que cuando construimos la cadena de plantilla temp_str
, usamos dos marcadores de posici贸n: $name
y $site
. El $
letrero realiza la sustituci贸n real y los identificadores ( name
y site
) se utilizan para asignar los marcadores de posici贸n a los objetos concretos que necesitamos insertar en la cadena de la plantilla.
La magia se completa cuando usamos el m茅todo substitute () para realizar la sustituci贸n y construir la cadena deseada. Piense substitute()
como si le dij茅ramos a Python, revise esta cadena y, si la encuentra $name
, reempl谩cela por John Doe
. Contin煤e buscando en la cadena y, si encuentra el identificador $site
, convi茅rtalo en Pharos.sh.com
.
Los nombres de los argumentos a los que pasamos .substitute()
deben coincidir con los identificadores que usamos en los marcadores de posici贸n de nuestra cadena de plantilla.
La diferencia m谩s importante entre Template
y el resto de las herramientas de sustituci贸n de cadenas disponibles en Python es que no se tiene en cuenta el tipo de argumento. Podemos pasar cualquier tipo de objeto que pueda convertirse en una cadena de Python v谩lida. La Template
clase convertir谩 autom谩ticamente estos objetos en cadenas y luego los insertar谩 en la cadena final.
Ahora que conocemos los conceptos b谩sicos sobre c贸mo usar la Template
clase Python , profundicemos en los detalles de su implementaci贸n para comprender mejor c贸mo funciona la clase internamente. Con este conocimiento a mano, podremos usar la clase de manera efectiva en nuestro c贸digo.
La cadena de plantilla
La cadena de plantilla es una cadena Python normal que incluye marcadores de posici贸n especiales. Como hemos visto antes, estos marcadores de posici贸n se crean mediante un $
signo, junto con un identificador de Python v谩lido. Una vez que tenemos una cadena de plantilla v谩lida, los marcadores de posici贸n pueden ser reemplazados por nuestros propios valores para crear una cadena m谩s elaborada.
De acuerdo con PEP 292 – Sustituciones de cadenas m谩s simples , se aplican las siguientes reglas para el uso de $
marcadores de posici贸n de inicio de sesi贸n:
$$
es un escape; se reemplaza con un solo$
$identifier
nombra un marcador de posici贸n de sustituci贸n que coincide con una clave de asignaci贸n de “identificador”. Por defecto, “identificador” debe deletrear un identificador de Python como se define en http://docs.python.org/reference/lexical_analysis.html#identifiers-and-keywords . El primer car谩cter no identificador despu茅s del$
car谩cter termina esta especificaci贸n de marcador de posici贸n.${identifier}
es equivalente a$identifier
. Es necesario cuando los caracteres identificadores v谩lidos siguen al marcador de posici贸n pero no forman parte del marcador de posici贸n, por ejemplo"${noun}ification"
. ( Fuente )
Codifiquemos algunos ejemplos para comprender mejor c贸mo funcionan estas reglas.
Comenzaremos con un ejemplo de c贸mo podemos escapar de la $
se帽al. Suponga que estamos tratando con monedas y necesitamos tener el signo de d贸lar en nuestras cadenas resultantes. Podemos duplicar el $
signo para escapar en la cadena de la plantilla de la siguiente manera:
>>> budget = Template('The $time budget for investment is $$$amount')
>>> budget.substitute(time="monthly", amount="1,000.00")
'The monthly budget for investment is $1,000.00'
Tenga en cuenta que no es necesario agregar un espacio adicional entre el signo de escape y el siguiente marcador de posici贸n como lo hicimos en $$$amount
. Las plantillas son lo suficientemente inteligentes como para poder escapar de la $
se帽al correctamente.
La segunda regla establece los conceptos b谩sicos para crear un marcador de posici贸n v谩lido en nuestras cadenas de plantilla. Cada marcador de posici贸n debe construirse utilizando el $
car谩cter seguido de un identificador de Python v谩lido. Eche un vistazo al siguiente ejemplo:
>>> template = Template('$what, $who!')
>>> template.substitute(what="Hello", who='World')
'Hello, World!'
Aqu铆, ambos marcadores de posici贸n se forman utilizando identificadores de Python v谩lidos ( what
y who
). Tambi茅n tenga en cuenta que, como se indica en la segunda regla, el primer car谩cter no identificador termina el marcador de posici贸n como puede ver $who!
donde el car谩cter !
no es parte del marcador de posici贸n, sino de la cadena final.
Puede haber situaciones en las que necesitemos sustituir parcialmente una palabra en una cadena. Esa es la raz贸n por la que tenemos una segunda opci贸n para crear un marcador de posici贸n. La tercera regla establece que ${identifier}
es equivalente $identifier
y debe usarse cuando los caracteres identificadores v谩lidos siguen al marcador de posici贸n pero no son parte del marcador de posici贸n en s铆.
Supongamos que necesitamos automatizar la creaci贸n de archivos que contengan informaci贸n comercial sobre los productos de nuestra empresa. Los archivos se nombran siguiendo un patr贸n que incluye el c贸digo del producto, el nombre y el lote de producci贸n, todos ellos separados por un car谩cter de subrayado ( _
). Considere el siguiente ejemplo:
>>> filename_temp = Template('$code_$product_$batch.xlsx')
>>> filename_temp.substitute(code="001", product="Apple_Juice", batch="zx.001.2020")
Traceback (most recent call last):
...
KeyError: 'code_'
Dado que _
es un car谩cter identificador de Python v谩lido, nuestra cadena de plantilla no funciona como se esperaba y Template
genera un KeyError
. Para corregir este problema, podemos usar la notaci贸n entre llaves ( ${identifier}
) y construir nuestros marcadores de posici贸n de la siguiente manera:
>>> filename_temp = Template('${code}_${product}_$batch.xlsx')
>>> filename_temp.substitute(code="001", product="Apple_Juice", batch="zx.001.2020")
'001_Apple_Juice_zx.001.2020.xlsx'
隆Ahora la plantilla funciona correctamente! Eso es porque las llaves separan correctamente nuestros identificadores del _
personaje. Vale la pena se帽alar que s贸lo tenemos que utilizar la notaci贸n prepar贸 para code
y product
y no para batch
porque el .
car谩cter que sigue batch
no es un car谩cter identificador v谩lido en Python.
Finalmente, la cadena de plantilla se almacena en la template
propiedad de la instancia. Repasemos el Hello, World!
ejemplo, pero esta vez vamos a modificar template
un poco:
>>> template = Template('$what, $who!') # Original template
>>> template.template="My $what, $who template" # Modified template
>>> template.template
'My $what, $who template'
>>> template.substitute(what="Hello", who='World')
'My Hello, World template'
Dado que Python no restringe el acceso a los atributos de la instancia, podemos modificar nuestra cadena de plantilla para satisfacer nuestras necesidades cuando queramos. Sin embargo, esta no es una pr谩ctica com煤n cuando se usa la Template
clase Python .
Es mejor crear nuevas instancias de Template
para cada cadena de plantilla diferente que usamos en nuestro c贸digo. De esta manera, evitaremos algunos errores sutiles y dif铆ciles de encontrar relacionados con el uso de cadenas de plantillas inciertas.
El m茅todo sustituto ()
Hasta ahora, hemos estado usando el substitute()
m茅todo en una Template
instancia para realizar la sustituci贸n de cadenas. Este m茅todo reemplaza los marcadores de posici贸n en una cadena de plantilla usando argumentos de palabras clave o usando una asignaci贸n que contiene pares identificador-valor.
Los argumentos de la palabra clave o los identificadores en la asignaci贸n deben coincidir con los identificadores utilizados para definir los marcadores de posici贸n en la cadena de plantilla. Los valores pueden ser de cualquier tipo de Python que se convierta correctamente en una cadena.
Dado que hemos cubierto el uso de argumentos de palabras clave en ejemplos anteriores, ahora concentr茅monos en el uso de diccionarios. He aqu铆 un ejemplo:
>>> template = Template('Hi $name, welcome to $site')
>>> mapping = {'name': 'John Doe', 'site': 'Pharos.sh.com'}
>>> template.substitute(**mapping)
'Hi John Doe, welcome to Pharos.sh.com'
Cuando usamos los diccionarios como argumentos con substitute()
, tenemos que utilizar el operador de desembalaje diccionario: **
. Este operador descomprimir谩 los pares clave-valor en argumentos de palabras clave que se utilizar谩n para sustituir los marcadores de posici贸n coincidentes en la cadena de la plantilla.
Errores comunes de plantilla
Hay algunos errores comunes que podemos introducir inadvertidamente cuando usamos la Template
clase Python .
Por ejemplo, a KeyError
se genera cada vez que proporcionamos un conjunto incompleto de argumentos a substitute()
. Considere el siguiente c贸digo que usa un conjunto incompleto de argumentos:
>>> template = Template('Hi $name, welcome to $site')
>>> template.substitute(name="Jane Doe")
Traceback (most recent call last):
...
KeyError: 'site'
Si llamamos substitute()
con un conjunto de argumentos que no coincide con todos los marcadores de posici贸n en nuestra cadena de plantilla, obtendremos un KeyError
.
Si usamos un identificador de Python no v谩lido en algunos de nuestros marcadores de posici贸n, recibiremos un ValueError
mensaje que nos dir谩 que el marcador de posici贸n es incorrecto.
Tome este ejemplo donde usamos un identificador no v谩lido, $0name
como marcador de posici贸n en lugar de $name
.
>>> template = Template('Hi $0name, welcome to $site')
>>> template.substitute(name="Jane Doe", site="Pharos.sh.com")
Traceback (most recent call last):
...
ValueError: Invalid placeholder in string: line 1, col 4
Solo cuando el Template
objeto lee la cadena de plantilla para realizar la sustituci贸n, descubre el identificador no v谩lido. Inmediatamente genera un ValueError
. Tenga en cuenta que 0name
no es un identificador o nombre de Python v谩lido porque comienza con un d铆gito.
El m茅todo safe_substitute ()
La Template
clase Python tiene un segundo m茅todo que podemos usar para realizar la sustituci贸n de cadenas. El m茅todo se llama safe_substitute()
. Funciona de manera similar, substitute()
pero cuando usamos un conjunto de argumentos incompletos o que no coinciden, el m茅todo no aumenta KeyError
.
En este caso, el marcador de posici贸n que falta o que no coincide aparece sin cambios en la cadena final.
As铆 es como safe_substitute()
funciona el uso de un conjunto incompleto de argumentos ( site
faltar谩n):
>>> template = Template('Hi $name, welcome to $site')
>>> template.safe_substitute(name="John Doe")
'Hi John Doe, welcome to $site'
Aqu铆, primero llamamos safe_substitute()
usando un conjunto incompleto de argumentos. La cadena resultante contiene el marcador de posici贸n original $site
, pero no KeyError
se genera.
Personalizar la clase de plantilla de Python
La Template
clase Python est谩 dise帽ada para subclases y personalizaci贸n. Esto nos permite modificar los patrones de expresi贸n regular y otros atributos de la clase para satisfacer nuestras necesidades espec铆ficas.
En esta secci贸n, cubriremos c贸mo personalizar algunos de los atributos m谩s importantes de la clase y c贸mo esto impacta el comportamiento general de nuestros Template
objetos. Comencemos con el atributo de clase .delimiter
.
Usar un delimitador diferente
El atributo de clase delimiter
contiene el car谩cter utilizado como car谩cter inicial del marcador de posici贸n. Como hemos visto hasta ahora, su valor predeterminado es $
.
Dado que la Template
clase Python est谩 dise帽ada para la herencia, podemos crear una subclase Template
y cambiar el valor predeterminado de delimiter
anul谩ndolo. Eche un vistazo al siguiente ejemplo donde anulamos el delimitador para usar en #
lugar de $
:
from string import Template
class MyTemplate(Template):
delimiter="https://Pharos.sh.com/formatting-strings-with-the-python-template-class/#"
template = MyTemplate('Hi #name, welcome to #site')
print(template.substitute(name="Jane Doe", site="Pharos.sh.com"))
# Output:
# 'Hi Jane Doe, welcome to Pharos.sh.com'
# Escape operations also work
tag = MyTemplate('This is a Twitter hashtag: ###hashtag')
print(tag.substitute(hashtag='Python'))
# Output:
# 'This is a Twitter hashtag: #Python'
Podemos usar nuestra MyTemplate
clase tal como usamos la Template
clase Python normal . Sin embargo, ahora debemos usar en #
lugar de $
construir nuestros marcadores de posici贸n. Esto puede ser 煤til cuando trabajamos con cadenas que manejan muchos signos de d贸lar, por ejemplo, cuando se trata de monedas.
Nota : Do no reemplazar una delimiter
con una expresi贸n regular. La clase de plantilla escapa autom谩ticamente del delimitador. Por lo tanto, si usamos una expresi贸n regular delimiter
, es muy probable que nuestra costumbre Template
no funcione correctamente.
Cambiar lo que califica como identificador
El idpattern
atributo de clase contiene una expresi贸n regular que se usa para validar la segunda mitad de un marcador de posici贸n en una cadena de plantilla. En otras palabras, idpattern
valida que los identificadores que usamos en nuestros marcadores de posici贸n son identificadores de Python v谩lidos. El valor predeterminado de idpattern
es r'(?-i:[_a-zA-Z][_a-zA-Z0-9]*)'
.
Podemos crear una subclase Template
y usar nuestro propio patr贸n de expresi贸n regular para idpattern
. Supongamos que necesitamos restringir los identificadores a nombres que no contienen guiones bajos ( _
) ni d铆gitos ( [0-9]
). Para hacer esto, podemos anular idpattern
y eliminar estos caracteres del patr贸n de la siguiente manera:
from string import Template
class MyTemplate(Template):
idpattern = r'(?-i:[a-zA-Z][a-zA-Z]*)'
# Underscores are not allowed
template = MyTemplate('$name_underscore not allowed')
print(template.substitute(name_underscore="Jane Doe"))
Si ejecutamos este c贸digo obtendremos este error:
Traceback (most recent call last):
...
KeyError: 'name'
Podemos confirmar que tampoco se permiten d铆gitos:
template = MyTemplate('$python3 digits not allowed')
print(template.substitute(python3='Python version 3.x'))
El error ser谩:
Traceback (most recent call last):
...
KeyError: 'python'
Dado que el gui贸n bajo y los d铆gitos no est谩n incluidos en nuestra costumbre idpattern
, el Template
objeto aplica la segunda regla y rompe el marcador de posici贸n con el primer car谩cter no identificador despu茅s $
. Por eso obtenemos un KeyError
en cada caso.
Creaci贸n de subclases de plantillas avanzadas
Puede haber situaciones en las que necesitamos para modificar el comportamiento del Python Template
clase, pero anulando delimiter
, idpattern
o ambos no es suficiente. En estos casos, podemos ir m谩s all谩 y anular el pattern
atributo de clase para definir una expresi贸n regular completamente nueva para nuestras Template
subclases personalizadas .
Si decide utilizar una expresi贸n regular completamente nueva para pattern
, debe proporcionar una expresi贸n regular con cuatro grupos con nombre:
escaped
coincide con la secuencia de escape del delimitador, como en$$
named
coincide con el delimitador y un identificador de Python v谩lido, como en$identifier
braced
coincide con el delimitador y un identificador de Python v谩lido usando llaves, como en${identifier}
invalid
coincide con otros delimitadores mal formados, como en$0site
La pattern
propiedad contiene un objeto de expresi贸n regular compilado. Sin embargo, es posible inspeccionar la cadena de expresi贸n regular original accediendo al pattern
atributo de la pattern
propiedad. Consulte el siguiente c贸digo:
>>> template = Template('$name')
>>> print(template.pattern.pattern)
$(?:
(?P<escaped>$) | # Escape sequence of two delimiters
(?P<named>(?-i:[_a-zA-Z][_a-zA-Z0-9]*)) | # delimiter and a Python identifier
{(?P<braced>(?-i:[_a-zA-Z][_a-zA-Z0-9]*))} | # delimiter and a braced identifier
(?P<invalid>) # Other ill-formed delimiter exprs
)
Este c贸digo genera la cadena predeterminada que se usa para compilar el pattern
atributo de clase. En este caso, podemos ver claramente los cuatro grupos nombrados que se ajustan a la expresi贸n regular predeterminada. Como se indic贸 anteriormente, si necesitamos personalizar profundamente el comportamiento de Template
, entonces deber铆amos proporcionar estos mismos cuatro grupos nombrados junto con expresiones regulares espec铆ficas para cada grupo.
Ejecutando c贸digo con eval () y exec ()
Nota: Las funciones integradas eval()
y exec()
pueden tener importantes implicaciones de seguridad cuando se utilizan con datos maliciosos. 隆脷selo con precauci贸n!
Esta 煤ltima secci贸n est谩 destinada a abrirle los ojos sobre lo poderosa que Template
puede ser la clase Python si la usamos junto con algunas funciones integradas de Python como eval()
y exec()
.
La eval()
funci贸n ejecuta una sola expresi贸n de Python y devuelve su resultado. La exec()
funci贸n tambi茅n ejecuta una expresi贸n de Python, pero nunca devuelve su valor. Normalmente lo usa exec()
cuando solo est谩 interesado en el efecto secundario de una expresi贸n, como un valor de variable cambiado, por ejemplo.
Los ejemplos que vamos a cubrir pueden parecer algo poco convencionales, pero estamos seguros de que puede encontrar algunos casos de uso interesantes para esta poderosa combinaci贸n de herramientas de Python. 隆Dan una idea de c贸mo funcionan las herramientas que generan c贸digo Python!
Para el primer ejemplo, usaremos una plantilla junto con eval()
para crear listas din谩micamente a trav茅s de una lista de comprensi贸n:
>>> template = Template('[$exp for item in $coll]')
>>> eval(template.substitute(exp='item ** 2', coll="[1, 2, 3, 4]"))
[1, 4, 9, 16]
>>> eval(template.substitute(exp='2 ** item', coll="[3, 4, 5, 6, 7, 8]"))
[8, 16, 32, 64, 128, 256]
>>> import math
>>> eval(template.substitute(expression='math.sqrt(item)', collection='[9, 16, 25]'))
[3.0, 4.0, 5.0]
Nuestro objeto de plantilla en este ejemplo contiene la sintaxis b谩sica de una lista de comprensi贸n. A partir de esta plantilla, podemos crear listas din谩micamente sustituyendo los marcadores de posici贸n con expresiones v谩lidas ( exp
) y colecciones ( coll
). Como paso final, ejecutamos la comprensi贸n usando eval()
.
Dado que no hay l铆mite en la complejidad de nuestras cadenas de plantilla, es posible crear cadenas de plantilla que contengan cualquier parte del c贸digo Python. Consideremos el siguiente ejemplo de c贸mo usar un Template
objeto para crear una clase completa:
from string import Template
_class_template = """
class ${klass}:
def __init__(self, name):
self.name = name
def ${method}(self):
print('Hi', self.name + ',', 'welcome to', '$site')
"""
template = Template(_class_template)
exec(template.substitute(klass="MyClass",
method='greet',
site="Pharos.sh.com"))
obj = MyClass("John Doe")
obj.greet()
Aqu铆, creamos una cadena de plantilla para contener una clase de Python completamente funcional. Posteriormente podemos usar esta plantilla para crear diferentes clases pero usando diferentes nombres seg煤n nuestras necesidades.
En este caso, exec()
crea la clase real y la trae a nuestro espacio de nombres actual. A partir de este punto, podemos usar libremente la clase como lo har铆amos con cualquier clase Python normal.
Aunque estos ejemplos son bastante b谩sicos, muestran lo poderosa que Template
puede ser la clase Python y c贸mo podemos aprovecharla para resolver problemas complejos de programaci贸n en Python.
Conclusi贸n
La Template
clase Python est谩 destinada a utilizarse para la sustituci贸n de cadenas o la interpolaci贸n de cadenas. La clase funciona con expresiones regulares y proporciona una interfaz potente y f谩cil de usar. Es una alternativa viable a otras opciones de sustituci贸n de cadenas integradas cuando se trata de crear plantillas complejas basadas en cadenas.
En este art铆culo, hemos aprendido c贸mo funciona la Template
clase Python . Tambi茅n aprendimos sobre los errores m谩s comunes que podemos introducir al usar Template
y c贸mo solucionarlos. Finalmente, cubrimos c贸mo personalizar la clase a trav茅s de subclases y c贸mo usarla para ejecutar c贸digo Python.
Con este conocimiento a la mano, estamos en mejores condiciones para usar de manera efectiva la Template
clase Python para realizar la interpolaci贸n o sustituci贸n de cadenas en nuestro c贸digo.