martes, 10 de septiembre de 2024

TEXTOS. Cadenas.

Concatenar cadenas

No es esta la primera vez que hablo de cadenas y de las formas de concatenación disponibles en Python, pero la revisión de lo hecho y el estudio del tema me han convencido de la necesidad de tratar de nuevo esta cuestión, profundizando más en ella y analizando con cierto detalle los diferentes modos en que se planea este procedimiento de generación de código. La importancia que tiene en procesos de automatización documental justifica sobradamente que preste especial atención al tema.


Tampoco es sobre Python el único lenguaje respecto al cual trato el tema de la concatenación, ya que también dediqué alguna que otra entrada para tratar el tema tanto respecto a las funciones nativas de LibreOffice-Calc como en el lenguaje OOo Basic. De hecho, si vuelvo ahora sobre esta cuestión en Python es precisamente a consecuencia de haber trabajado sobre el mismo tema tomando como referencia el servicio LO-Calc.

Es posible que, al retomar el tema, se produzcan repeticiones de contenido que tal vez depure en una posterior revisión del conjunto de las entradas, pero que ahora sólo puedo lamentar como consecuencia lógica del paso del tiempo y de la complejidad de la temática. Te ruego disculpas, lector, por la molestia que esta repetición te pueda suponer.

Como vimos, en OOo Basic se utilizan dos formas básicas y se puede decir que intercambiables de concatenar cadenas: mediante + y & podemos concatenar tantos segmentos de texto y/o variables como deseemos, sin más límite que la extensión del párrafo que queramos crear y la inteligibilidad del código resultante, a cual, al ser un proceso repetitivo puede ser poco elegante, pero no necesariamente disfuncional o ininteligible.

Recuerdo que en su momento recomendé utilizar en OOo Basic & para concatenar string (y variables string) y reservar + para la operación de sumar, como forma de facilitar la lectura del código, pero se trata de una opción y preferencia personal, sin que exista (que yo sepa) motivación técnica específica para defender esta opción.

Vimos en una entrada anterior que en Python también podemos concatenar cadenas de forma directa mediante , y +, pero presentan limitaciones y restricciones que no se observan en OOo Basic, por lo que son fórmulas no recomendables en cuanto la combinación de cadenas adquiere cierto grado de complejidad, siendo para ello suficiente con que queramos combinar string literal con variables.

Dado que Python es un lenguaje en desarrollo, las versiones que se han ido creando con el paso del tiempo han ido incorporando soluciones diferentes y dejando atrás como obsoletas las que se mostraron limitadas, aunque no por ello dejan de estar disponibles, como pasa con los modelos básicos anteriores.

El último desarrollo en estas funcionalidades son las llamadas cadenas f, de las cuales ya te hablé en esta entrada, pero es posible que dejara por el camino procedimientos interesantes y aun vigentes que surgieron en su momento como respuesta a las demandas del procedimiento. Trataré en esta entrada de tapar estos huecos.

Todo esto es para confirmar que el tema de la concatenación de cadenas ya ha sido tratado en este blog, pero, una vez dicho, paso a indicar que, lo que sigue constituye una reformulación sistemática y ampliada de lo que esas entradas pueden haber recogido, a la vez que una continuidad y conclusión de lo explicado respecto a LibreOffice y OOo Basic en otro blog.

Así que paso a exponer paso a paso el análisis de las diferentes formas de concatenar cadenas en Python. Tomo como referencia lo que expone Alfredo Sánchez Alberca en su material on-line Aprendiendo con Alf, al que remito y del que me declaro deudor.

Expongo en primer lugar diferentes formas de uso de conectores [, y +], que son las formas equivalentes a lo que en OOo Basic son [& y +] respectivamente.

Primera formulación. Concatenación de dos literales con separación de coma (,).

print("Hola","Javier") -> Hola Javier

Segunda formulación. Concatenación de literal y variable con separación de coma (,).

nom = "Javier"
print("Hola",nom) -> Hola Javier

Tercera formulación. Concatenación de dos variables con separación de coma (,).

sal = "Hola" 
nom = "Javier"
print("Hola",nom) -> Hola Javier

Cuarta formulación. Concatenación de dos literales con separación de signo (+).

print("Hola"+"Javier") -> HolaJavier

Quinta formulación. Concatenación de literal y variable con separación de signo (+).

print("Hola"+nom) -> HolaJavier

Sexta formulación. Concatenación de dos variable con separación de signo (+).

print(sal+nom) -> HolaJavier

NOTA 1 -> Ambas fórmulas funcionan igual (salvo en determinados casos) siendo los contenidos string, con la única diferencia que el uso de coma (,) genera un espacio de separación entre los componentes, mientras que el uso del signo más (+) no lo genera. Este caso es necesario implementarlo como parte del literal o como cadena formada por un espacio (' ' o " ") y posicionada entre los demás componentes.

Séptima formulación. Concatenación de literal string y literal numeral con coma (,)

print("Número",123) -> Número 123

 Octava formulación. Concatenación de literal string y literal numeral con signo (+)

print("Número"+123) -> [TypeError: can only concatenate str (not "int") to str]

print("Número" + str(123)) -> Número 123

NOTA 2 -> Uno de los casos en los que (,) y (+) no funcionan igual es cuando queremos combinar texto y números dentro de la concatenación. En ese caso mientras que podemos hacerlo sin problemas si usamos (,) (como en Séptima), no funciona si usamos (+) (como en Octava). En ésta opción debemos convertir el número antes a string mediante la función str().

En segundo lugar vamos a explicar el uso de la primera fórmula de formateo de cadenas que se empleó en Python desde sus primeras versiones como lenguaje de programación. Se base en el uso del signo %s, siendo % el operador que hace de marcador y la referencia a la cadena que se anexa.  %s que se sitúa necesariamente dentro de la cadena en la posición en la que se desea ubicar el segundo segmento de la cadena y se puede utilizar tantas veces como sea necesario en combinaciones complejas. En estos casos la concatenación utiliza también los signos de las configuraciones ya vistas, especialmente el signo [,]. 

Al igual que los concatenadores [, y +] presenta también varias formulaciones, que enumeraré siguiendo el orden ya establecido.

Novena formulación. Literal cadena con unión de segundo elemento (cadena).

print('Hola, me llamo %s' % 'Javier') -> Hola, me llamo Javier

Décima formulación. Literal cadena con unión de segundo elemento (número).

print('Número %s' % 123) -> Número 123 

Undécima formulación. Literal cadena con unión de variable.

nom = 'Javier'
print('Hola, me llamo %s' % nom) -> Hola, me llamo Javier 

NOTA 3 -> En los tres casos el funcionamiento el correcto y el uso y posicionamiento del %s sigue el mismo patrón (se sitúa al final del primer literal), lo es debido al tipo simple de concatenación, pero no responde a una exigencia de la sintaxis de código.

Décimo segunda formulación.  Fórmulas mixtas y complejas.

A. print('Hola %s' % nom,',buenos días') -> Hola Javier, buenos días

B. print('Hola %s','buenos días' %nom) -> TypeError: not all arguments converted during string formatting

C. print('Hola %s', buenos días', %nom) -> Hola Javier, buenos días

D. print('Hola %s' % nom,',buenos días') -> Hola Javier, buenos días

E. annos = 24
    print('Hola %s' % nombre, ', tienes %s' % annos , 'de edad (eso quisieras)') -> Hola      Javier, tienes 24 años de edad (eso quisieras)

F. print('Hola %s, tienes %s' % nom % annos, 'de edad (eso quisieras)') ->TypeError:       not enough arguments for format string

NOTA 4 -> Lo que revela esta formulación (y sus variantes) es que es posible concatenar más de una cadena (o componente, ya que también es válido para números y para variables) siempre que se respete la sintaxis de %s que obliga a:

  • Posicionar el marcador en la posición requerida por la formulación deseada de la cadena resultante de la concatenación (A)
  • Uso de tantos marcadores como sea necesario en función de lo deseado (C, E)
  • Pero con restricción de posicionamiento: cada uso de %s debe posicionarse tras la cadena afectada (B,D) y señalar sin ambigüedad a un contenido a anexar, no admitiendo dos marcadores dentro de una subcadena ni dos referencias sucesivas (F)
A pesar de la sencillez de este procedimiento, ya podemos advertir que su utilidad supera ampliamente los recursos disponibles en OOo Basic en cuanto al manejo de cadenas. Gracias a esto es posible idear procedimientos de personalización de documentos basados en la técnica 'cloze': sobre un texto base, los contenidos a personalizar son asumidos por variables, las cuales, a su vez, son cumplimentadas por el usuario de modo interactivo mediante la función input (ver ejemplo). Y no acaban aquí las opciones.

Efectivamente, una tercera opción (o procedimiento, si se prefiere) de formateo de cadenas es el método cadena.format(valores):
  • La posición de los valores en la cadena-base se identifican con {}
  • Esta cadena se sitúa al inicio de la secuencia
  • El texto de reemplazo va precedido de la id de a función format()
  • ... y se puede hacer por posición de forma implícita o explicitando los valores posicionales...
  • ... o bien utilizando la forma diccionario clave:valor
Veamos ejemplos prácticos de todo ello

Primera forma.

print('Me llamo {}, vivo en {} y estudio {} en {}'.format('Elvira','Oviedo','Veterinaria', 'León.))

-> Me llamo Elvira, vivo en Oviedo y estudio Veterinaria en León.

 Segunda forma.

print('Dame {}, el {} y los {}'.format('la llave inglesa', 'destornillador', 'alicates'))
print('Dame{2}, la {0} y el {1}'.format('llave inglesa', 'destornillador', ' los alicates'))

NOTA 5 -> Las llaves {} indican la posición de los valores de .format(). Tanto en la primera forma como en la primera línea de código de la segunda, aunque no se especifica nada dentro de las llaves, es el orden mismo en que aparecen los valores .format() el que sirve para indicar la posición que ocuparán en el texto. LO que hago con la segunda línea de código de la segunda forma es alterar ese orden indicando dentro de las llaves un número-índice, que es la posición de las entradas en .format().

El texto resultante de 2.1 será  Dame la llave inglesa, el destornillador y los alicates y el de 2.2 Dame los alicates, la llave inglesa y el destornillador.

Tercera forma. Uso de variables.

 

nombre = 'Julia'
localidad = 'Gijón'
estudios = 'Albañilería'

print('Me llamo {}, vivo en {} y estudio {}'.format(nombre,localidad,estudios))

print('Me llamo {1}, vivo en {2} y estudio {0}'.format(nombre,localidad,estudios))

NOTA 6 -> En esta tercera forma sustituimos los valores directos por variables (lo que nos permite, por cierto. modificar interactivamente el contenido) y después empleamos las variables como contenido de .format(). las dos expresiones permiten comprobar que el uso de variables no modifica las propiedades y el modo de funcionar del procedimiento.

Cuarta forma.  Clave:Valor

print('Me llamo {nom}, vivo en {local} y estudio {estud}' .format (nom=nombre, local=localidad, estud=estudios))

NOTA 7 -> Sobre la misma base de contenido que en 3, ahora en 4  expongo la forma más evolucionada del procedimiento: una clave identifica y se diferencia del valor o contenido que no es otro que el nombre de las variables. Esa clave es la que se escribe dentro de las llaves, en sustitución y como alternativa del valor-índice empleado en formas previas. La ventaja de esta fórmula es que se asocia al uso de diccionarios.

Finalmente, la cuarta opción ya ha sido específicamente tratada en una entrada de este blog, por lo que no me detendré demasiado en ella. Se trata de las llamadas cadenas f (f-string o formatos literales), que se caracterizan porque su sintaxis incluye F o f al inicio de la cadena, en literales, antes de las comillas. Veamos un ejemplo:

nom1 = 'Juana'
nom2 = 'Lucas'
print(f'Una niña llamada {nom1} juega con un niño llamado {nom2}')

print(f'{nom1}') -> Juana

NOTA 7 ->  f-string y .format() son incompatibles, esto es: no se pueden usar en la misma concatenación. En el ejemplo anterior, tercera línea, tanto si eliminamos f como si añadimos format() para identificar la segunda variable o cualquier otra, se produce un funcionamiento anómalo.  Obsérvese en la cuarta línea cómo se utiliza f-string cuando iniciamos la cadena con una variable: necesitamos incluirla en una cadena literal, esto es: dentro de comillas (' '/" ") e identificada como tal variable entre llaves. Cualquier otra sintaxis provoca error o respuesta no deseada.

Contamos aun con una última forma de concatenar variables con cadenas, aunque esta quinta opción es en realidad una opción que podemos considerar complementaria de las anteriores, ya que no forma parte de los recursos comunes del lenguaje y precisa importar una librería específica llamada Template y las funciones asociadas a string.

 from string import Template

print(Template('Me gusta estudiar $leng').substitute(leng='Python'))

print(Template('Me gusta $accion $leng').substitute(accion='estudiar', leng='Python'))

NOTA 8 -> Puedes observar en estos dos ejemplos la sintaxis del procedimiento, en cierto modo recuerda al uso de funciones y más específicamente a .format():

  1. Primero llamamos a la función Template()
  2. Después incluimos dentro de cadena los marcadores, que se identifican mediante la expresión $NomVar.
  3. Y finalmente, mediante la función .susbtitude(), como parámetros de ellas en, y en formato clave:valor, damos contenido a las variables.