Formateo de cadenas con la clase de plantillas de Python

    Introducci贸n

    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 Templateclase 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 Templateclase Python se agreg贸 al stringm贸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, $namey $agese considerar铆an marcadores de posici贸n v谩lidos.

    Para usar la Templateclase Python en nuestro c贸digo, necesitamos:

    • Importar Templatedesde el stringm贸dulo
    • Crea una cadena de plantilla v谩lida
    • Crear una instancia Templateusando 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 Templateclase 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: $namey $site. El $letrero realiza la sustituci贸n real y los identificadores ( namey 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 Templatey 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 Templateclase 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 Templateclase 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$
    • $identifiernombra 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 ( whaty 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 $identifiery 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 Templategenera 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 codey producty no para batchporque el .car谩cter que sigue batchno es un car谩cter identificador v谩lido en Python.

    Finalmente, la cadena de plantilla se almacena en la templatepropiedad de la instancia. Repasemos el Hello, World!ejemplo, pero esta vez vamos a modificar templateun 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 Templateclase Python .

    Es mejor crear nuevas instancias de Templatepara 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 Templateinstancia 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 Templateclase Python .

    Por ejemplo, a KeyErrorse 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 ValueErrormensaje que nos dir谩 que el marcador de posici贸n es incorrecto.

    Tome este ejemplo donde usamos un identificador no v谩lido, $0namecomo 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 Templateobjeto lee la cadena de plantilla para realizar la sustituci贸n, descubre el identificador no v谩lido. Inmediatamente genera un ValueError. Tenga en cuenta que 0nameno es un identificador o nombre de Python v谩lido porque comienza con un d铆gito.

    El m茅todo safe_substitute ()

    La Templateclase 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 ( sitefaltar谩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 KeyErrorse genera.

    Personalizar la clase de plantilla de Python

    La Templateclase 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 Templateobjetos. Comencemos con el atributo de clase .delimiter.

    Usar un delimitador diferente

    El atributo de clase delimitercontiene 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 Templateclase Python est谩 dise帽ada para la herencia, podemos crear una subclase Templatey cambiar el valor predeterminado de delimiteranul谩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 MyTemplateclase tal como usamos la Templateclase 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 delimitercon 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 Templateno funcione correctamente.

    Cambiar lo que califica como identificador

    El idpatternatributo 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, idpatternvalida que los identificadores que usamos en nuestros marcadores de posici贸n son identificadores de Python v谩lidos. El valor predeterminado de idpatternes r'(?-i:[_a-zA-Z][_a-zA-Z0-9]*)'.

    Podemos crear una subclase Templatey 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 idpatterny 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 Templateobjeto aplica la segunda regla y rompe el marcador de posici贸n con el primer car谩cter no identificador despu茅s $. Por eso obtenemos un KeyErroren cada caso.

    Creaci贸n de subclases de plantillas avanzadas

    Puede haber situaciones en las que necesitamos para modificar el comportamiento del Python Templateclase, pero anulando delimiter, idpatterno ambos no es suficiente. En estos casos, podemos ir m谩s all谩 y anular el patternatributo de clase para definir una expresi贸n regular completamente nueva para nuestras Templatesubclases 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 patternpropiedad contiene un objeto de expresi贸n regular compilado. Sin embargo, es posible inspeccionar la cadena de expresi贸n regular original accediendo al patternatributo de la patternpropiedad. 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 patternatributo 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 Templatepuede 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 Templateobjeto 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 Templatepuede ser la clase Python y c贸mo podemos aprovecharla para resolver problemas complejos de programaci贸n en Python.

    Conclusi贸n

    La Templateclase 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 Templateclase Python . Tambi茅n aprendimos sobre los errores m谩s comunes que podemos introducir al usar Templatey 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 Templateclase Python para realizar la interpolaci贸n o sustituci贸n de cadenas en nuestro c贸digo.

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *