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 *