Substring en Python: Cómo Extraer Subcadenas de Texto

substring-en-python

A menudo necesitamos obtener una subcadena de un texto determinado, de forma que lo primero que tendemos a hacer, en cualquier lenguaje de programación es buscar una función que nos lo permita. ¿Existe substring, substr o algo parecido en Python para obtener una subcadena? Esta es una pregunta muy frecuente cuando hablamos de Python, porque no existe dicha función, así que voy a aclararlo en este artículo.

Los strings o cadenas de texto en Python son secuencias de caracteres que se pueden tratar como listas de solo lectura. Para obtener un substring basta con usar la notación de porciones. Por ejemplo, para acceder a la subcadena entre los carácteres de las posiciones ini y fin del string cadena haremos lo siguiente: cadena[ini:fin].

Esta es la manera directa de obtener una subcadena cuando conocemos la posición de la misma. Si no es así, te muestro a continuación otras maneras de acceder a subcadenas, como por ejemplo cómo obtener una subcadena entre dos caracteres concretos.

También veremos otras operaciones muy interesantes como por ejemplo localizar una subcadena, eliminarla, reemplazarla por otra, comprobar si existe, etc., así como un ejemplo final que permita aclarar todos los conceptos aquí explicados.

Cómo hacer un substring en Python

Aunque pueden darse miles de casuísticas diferentes, lo más habitual es querer obtener un substring de un string a partir de dos posiciones concretas o de dos caracteres dados. Veamos cada opción.

Substring por índice

Esta es el caso más habitual, y como ya introduje al comienzo del artículo, podemos pensar en las cadenas de texto como en listas de solo lectura, de forma que podemos utilizar la notación de rangos o porciones (corchetes) y acceder directamente a la subcadena que nos interesa, teniendo siempre en cuenta que en esta notación el primer índice está incluido y el segundo no.

cadena = 'aeiou'

ini = 1 #posición inicial de la subcadena
fin = 3 #posición final de la subcadena (excluida)

subcadena = cadena[ini:fin]

De esta manera, si ahora imprimimos por pantalla el valor de la variable subcadena obtendremos lo siguiente:

ei

Puedes jugar con toda las opciones que nos ofrece la notación de porciones para obtener distintos tipos de subcadena. Por ejemplo, obtener toda la subcadena desde el principio del string hasta una posición dada, obtenerla desde una posición dada hasta el final u obtener un número concreto de caracteres al final de la cadena. Lo vemos:

cadena = 'esta es nuestra cadena'

#obtenemos la subcadena desde el principio hasta la posición 10
subcadena = cadena[:10]
print(subcadena)

#obtenemos la subcadena desde la posición 10 hasta el final
subcadena = cadena[10:]
print(subcadena)

#obtenemos los últimos 10 caracteres de la cadea
subcadena = cadena[-10:]
print(subcadena)

El script anterior generará el siguiente resultado por pantalla:

esta es nu
estra cadena
tra cadena

Si estamos en una situación en la que necesitamos acceder al mismo substring de varias cadenas de texto, lo más aconsejable es utilizar un objeto del tipo slice (porción). Este objeto nos permite definir un rango o porción concreta y aplicarlo a varias cadenas sin necesidad de repetirlo una y otra vez, que tiene la principal ventaja de evitarnos errores en la escritura. Además, si necesitamos cambiar el rango, lo cambiamos una sola vez y no varias.

Por ejemplo, imagina que tenemos tres cadenas de texto y necesitamos acceder a la subcadena [6:11] de todas ellas. Crearemos el objeto slice para definir exactamente ese rango y lo aplicaremos a cada una de las cadenas de la siguiente manera:

cadena_1 = 'En un lugar de La Mancha'
cadena_2 = 'de cuyo nombre no quiero acordarme'
cadena_3 = 'no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero'

rango = slice(6,11)

subcadena_1 = cadena_1[rango]
subcadena_2 = cadena_2[rango]
subcadena_3 = cadena_3[rango]

print('La subcadena 1 es "' + subcadena_1 + '"')
print('La subcadena 2 es "' + subcadena_2 + '"')
print('La subcadena 3 es "' + subcadena_3 + '"')

El resultado que se obtiene por pantalla es:

La subcadena 1 es "lugar"
La subcadena 2 es "o nom"
La subcadena 3 es "mucho"

Substring por caracteres

Otro caso bastante común es el de querer extraer la subcadena que se encuentra entre dos caracteres concretos. Por ejemplo, vamos a obtener aquella subcadena que esté comprendida entre los caracteres c y h. Para ello podemos usar igualmente la notación de porciones, pero para eso necesitamos previamente averiguar los índices de posición de los caracteres que nos interesan.

Para saber cuál es el índice de un carácter dentro de una cadena podemos usar la función index que nos devuelve exactamente la posición en la que se encuentra. Así, nuestro ejemplo queda como sigue:

cadena = 'abcdefghijklmnopqrstuvwxyz'

indice_c = cadena.index('c') #obtenemos la posición del carácter c
indice_h = cadena.index('h') #obtenemos la posición del carácter h

subcadena = cadena[indice_c:indice_h]
print(subcadena)

#si queremos incluir en la subcadena el elemento de la posición 
#indice_h basta con sumar 1 a esa posición

subcadena = cadena[indice_c:indice_h + 1]
print(subcadena)

Y obtenemos este resultado por pantalla:

cdefg
cdefgh

Cómo comprobar si existe una subcadena

A veces no nos interesa obtener una subcadena sino, simplemente, saber si una cadena concreta contiene a otra. Por ejemplo, queremos saber si la cadena «En un lugar de La Mancha» contiene la subcadena «uga».

La manera más cómoda de hacerlo es utilizar el operador in, que nos indica con un booleano si la subcadena existe:

cadena = 'En un lugar de La Mancha'
subcadena = 'uga' #la subcadena que queremos

if subcadena in cadena:
	print('¡Existe!')

Es fácil de ver que el código anterior imprimirá «¡Existe!» por pantalla. El problema del operador in es que no nos dirá dónde se encuentra la subcadena, solo si se existe o no existe dentro de la cadena. Si necesitamos la posición se puede lograr de varias maneras que se exponen a continuación.

Sucede que a veces tenemos una lista de cadenas y necesitamos localiza aquellas que tienen un substring en concreto. Para ellos podríamos hacer lo siguiente que nos permite obtener otra lista con las cadenas que nos interesan:

cadena_1 = 'esta cadena contiene la palabra casa'
cadena_2 = 'esta cadena también contiene la palabra casa'
cadena_3 = 'sin embargo esta cadena no la contiene'
cadena_4 = 'esta también tiene la palabra casa'
subcadena = 'casa'

lista_de_cadenas = [cadena_1, cadena_2, cadena_3, cadena_4]

cadenas_con_casa = [lista for lista in lista_de_cadenas if subcadena in lista]

Si mostramos por pantalla la variable cadenas_con_casa obtendremos, como es de esperar, el siguiente resultado:

['esta cadena contiene la palabra casa', 'esta cadena también contiene la palabra casa', 'esta también tiene la palabra casa']

Cómo localizar u obtener la posición de una subcadena

La primera manera de localizar una subcadena ya la he introducido, pues se trata de la función index, que además de ser utilizada con caracteres individuales se puede usar con una subcadena, y que nos devuelve la posición de esa subcadena dentro de la cadena en la que estamos buscando:

cadena = 'En un lugar de La Mancha'
subcadena = 'uga' #la subcadena que queremos localizar

posicion = cadena.index(subcadena)
print('La posición de la subcadena es', posicion)

Este fragmento de código nos mostrará lo siguiente por pantalla:

La posición de la subcadena es 7

¡Cuidado! La función index falla si la subcadena a buscar no existe dentro de la cadena. En ese caso se lanza la excepción ValueError que, si no se captura, rompe la ejecución del programa.

Si no estamos seguros de que la subcadena exista dentro de la cadena, podemos usar previamente la función in para comprobarlo y así poder llamar a index de manera segura.

Otra alternativa es utilizar la función find, que funciona de manera muy parecida a index devolviendo la posición en la que se encuentra la subcadena, pero si la subcadena no existe, en lugar de fallar devuelve el valor -1 para indicar tal situación.

cadena = 'En un lugar de La Mancha'
subcadena = 'Castilla' #la subcadena que queremos localizar

posicion = cadena.find(subcadena)
print('La posición de la subcadena es', posicion)

Con el código anterior obtendremos lo siguiente por pantalla:

La posición de la subcadena es -1

Tanto index como find devuelven la posición de la primera aparición de la subcadena. Si queremos encontrar la posición de la última aparición podemos usar, de la misma manera, las funciones rindex y rfind.

Encontrar todas las apariciones de una subcadena

Si necesitamos encontrar todas las posiciones en las que aparece una subcadena dentro de una cadena podemos hacer uso, nuevamente de la función find, que permite indicarle como parámetros las posiciones de la cadena entre las que queremos buscar.

De esta manera podemos hacer un pequeño algoritmo que localice la primera posición de la subcadena y, posteriormente, haga una nueva búsqueda empezando a buscar desde la posición encontrada la primera vez (sin incluirla). Repitiendo este proceso hasta acabar con la cadena tendremos todas las posiciones. Si además ponemos este pequeño algoritmo dentro de una función obtendremos lo que sigue:

def encontrar_todas(cadena, subcadena):
	posiciones = []
	posicion = 0
	
	while posicion != -1:
		posicion = cadena.find(subcadena,posicion)
		if posicion != -1:
			posiciones.append(posicion)
			posicion += 1

	return posiciones


cadena = 'mi mama me mima'
subcadena = 'ma'

posiciones = encontrar_todas(cadena, subcadena)

print('Posiciones:', posiciones)

Al buscar todas las apariciones de la cadena «ma» dentro de «mi mama me mima» obtendremos lo siguiente por pantalla:

Posiciones: [3, 5, 13]

Cómo contar el número de apariciones de una subcadena

Hay una manera directa de contar el número de apariciones de una subcadena, que es utilizar la función count. Esta función nos devolverá dicho número pero sin tener en cuenta la subcadenas que se solapan. Es decir, en la cadena «amamantar» aparece la subcadena «ama» dos veces. Una en la posición 0 y otra en la posición 2: «amamantar» y «amamantar». Pero count nos devolverá que la subcadena aparece una sola vez, porque están solapadas.

Si necesitamos conocer el número de veces que aparece incluyendo aquellas que se solapan tendremos que hacerlo nosotros mismos. Una alternativa muy sencilla es utilizar la función encontrar_todas que ya te he presentado antes, y que tiene en cuenta los solapamientos, y contar el número de posiciones obtenidas. Aquí un ejemplo de ambas alternativas:

cadena = "mama que amamanta"
subcadena ="ama"

apariciones_sin_solapamiento = cadena.count(subcadena)
apariciones_con_solapamiento = len(encontrar_todas(cadena, subcadena))

print('Apariciones sin solapamiento', apariciones_sin_solapamiento)
print('Apariciones con solapamiento', apariciones_con_solapamiento)

Obtendremos el siguiente resultado:

Apariciones sin solapamiento 2
Apariciones con solapamiento 3

Cómo reemplazar una subcadena por otra

A veces lo que necesitamos es reemplazar o sustituir una subcadena por otra. Python ya nos proporciona una función para esto: replace. Esta función reemplaza todas las apariciones de una subcadena por otra.

Si no queremos reemplazar todas las apariciones, podemos indicarle, mediante un parámetro el número de apariciones que sí queremos sustituir.

Advertencia. En Python los strings o cadenas de texto son inmutables. Esto quiere decir que cualquier función para manipularlos no cambia la cadena original, sino que nos devuelve una nueva cadena con los cambios realizados.

cadena = 'Me gustan las manzanas y las peras pero prefiero las manzanas'
subcadena = 'manzanas'

nueva_cadena = cadena.replace(subcadena, 'fresas')
print(nueva_cadena)

#reemplazamos solo 1 aparición de la subcadena
nueva_cadena = cadena.replace(subcadena, 'cerezas', 1)
print(nueva_cadena)

#la cadena original permanece inalterada (los strings son inmutables)
print(cadena)

Y como resultado obtenemos:

Me gustan las fresas y las peras pero prefiero las fresas
Me gustan las cerezas y las peras pero prefiero las manzanas
Me gustan las manzanas y las peras pero prefiero las manzanas

Cómo eliminar una subcadena

Visto y entendido el mecanismo para sustituir una subcadena por otra, es fácil darse cuenta de que una manera sencilla de eliminar una subcadena es haciendo una sustitución por la cadena vacía de la siguiente manera: nueva_cadena = cadena.replace(subcadena, '').

Ejemplo completo con operaciones con subcadenas

Para terminar de aclararte el uso de las subcadenas te voy a presentar un ejemplo que podría tratarse de un caso real. Imaginemos el software de una tienda que genera los recibos de compra de los clientes con un formato concreto. Veamos un ejemplo:

Venta CV202005260456
Date: 26-05-2020
Artículo: Tornillos 35x6 100 unidades
Precio: 0.55 euros
Artículo: Tacos de metal 8x46 20 unidades
Precio: 2.46 euros
Artículo: Lubricante de maquinaria
Precio: 13.80 euros
Pago realizado
Total: 16.81 euros 

El problema con este formato de recibo es que nos parece demasiado feo y tiene algún error que otro, por eso queremos modificarlo aplicándole las siguientes mejoras:

  • La palabra «Venta» la sustituiremos por «VENTA» y haremos que aparezca subrayada con asteriscos. Además dejaremos un espacio con la siguiente línea.
  • Eliminaremos el código de venta, que es un código de 14 caracteres en la línea 2 y lo pondremos al final del recibo
  • La palabra «Date» es errónea porque necesitamos que aparezca en español. También arreglaremos la fecha para que se utilice como separador del día, mes y año el caracter «/» en lugar de un guión.También dejaremos un espacio con la siguiente línea.
  • Queremos eliminar las palabras «Artículo: » y «Precio: » de cada producto porque nos parecen repetitivas e innecesarias. Además queremos que entre cada par de artículo y precio quede una línea en blanco.
  • Necesitamos que antes del total del recibo aparezca el número de artículos que contiene el mismo.
  • Cambiaremos la palabra «Total » por «TOTAL»
  • Eliminaremos el texto «Pago realizado»
  • Finalmente corregiremos los precios para que en lugar del punto de separación de los decimales aparezca una coma y que, en lugar de «euros» ponga «EUR».

Todos estos cambios nos llevarán a un formato un poco más amigable, que será el siguiente:

VENTA
*****

Fecha: 26/05/2020

Tornillos 35x6 100 unidades
0,55 EUR

Tacos de metal 8x46 20 unidades
2,46 EUR

Lubricante de maquinaria
13,80 EUR

Número de artículos: 3
TOTAL: 16,81 EUR
CV202005260456

Todo este cambio de formato se puede realizar utilizando las funciones presentadas en este artículo. Aquí te presento el código comentado para que puedas ver como se resuelve este ejemplo:

recibo = '''Venta CV202005260456
Date: 26-05-2020
Artículo: Tornillos 35x6 100 unidades
Precio: 0.55 euros
Artículo: Tacos de metal 8x46 20 unidades
Precio: 2.46 euros
Artículo: Lubricante de maquinaria
Precio: 13.80 euros
Pago realizado
Total: 16.81 euros'''


#la manera de conocer el número de articulos es contar el número de
#apariciones de la palabra artículo antes de eliminarla del recibo

#necesitamos una variable para guardar el número de artículos
numero_articulos = recibo.count('Artículo')

#hay que extraer el código de venta, guardarlo y eliminarlo de su sitio
#sabemos que tiene 14 caracteres y que empieza en la posición 6
#(5 de la palabra Venta + 1 del espacio entre Venta y el código
codigo_venta = recibo[6:20]
recibo_formateado = recibo.replace(codigo_venta, '')

#reemplazamos Venta por VENTA con un salto de línea \n, la línea de asteriscos
#y un salto de línea más
recibo_formateado = recibo_formateado.replace('Venta', 'VENTA\n*****\n')

#corregirmos la palabra Date
recibo_formateado = recibo_formateado.replace('Date', 'Fecha')

#sustituimos la subcadena "Artículo: " por un salto de línea
#para dejar el espacio correspondiente entre un artículo y otr
recibo_formateado = recibo_formateado.replace('Artículo: ', '\n')

#eliminamos la subcadena "Precio: "
recibo_formateado = recibo_formateado.replace('Precio: ', '')

#sustituimos los puntos por comas, los guiones por barras
#y la palabra "euros" por "EUR".
recibo_formateado = recibo_formateado.replace('.', ',')
recibo_formateado = recibo_formateado.replace('-', '/')
recibo_formateado = recibo_formateado.replace('euros', 'EUR')

#eliminamos la subcadena Pago realizado.
recibo_formateado = recibo_formateado.replace('Pago realizado', '')

#generamos la subcadena  del número de articulos
subcadena_numero_articulos = 'Número de artículos: ' + str(numero_articulos)

#sustituimos la palabra "Total" por la subcadena del número de artículo,
#un salto de línea y la palabra TOTAL
recibo_formateado = recibo_formateado.replace('Total', subcadena_numero_articulos + '\nTOTAL')

#añadimos finalmente el código de venta al recibo
recibo_formateado = recibo_formateado + '\n' + codigo_venta

print(recibo_formateado)

Tabla de resumen de operaciones con subcadenas

FuncionesUsoEjemplo
Notación de porcionesObtener por índicescadena[2:5]
Notación de porciones, indexObtener por caracteresfin = cadena.index('c')
fin = cadena.index('h')
cadena[ini:fin]
inComprobar existenciasubcadena in cadena
index, rindexLocalizar cadena.index(subcadena)
find, rfindLocalizarcadena.find(subcadena)
countContar aparicionescadena.count(subcadena)
replaceReemplazar por subcadena nuevacadena.replace(subcadena, subcadena_nueva)
replaceEliminarcadena.replace(subcadena, '')
Tabla de resumen de operaciones básicas con subcadenas de texto en Python

La Hoja de Referencia de Python – ¡Gratis!

La Hoja de Referencia de Python - Código Pitón
Consigue trucos, consejos y actualizaciones y, por supuesto, la Hoja de Referencia de Python gratis.



Antes de suscribirte consulta aquí la Información Básica sobre Protección de Datos. Responsable de los datos: Laura Otero Moreira. Finalidad de la recogida y tratamiento de los datos personales: enviarte boletín informativo de Python y comunicaciones comerciales. Legitimación: tu consentimiento. Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (MailRelay). Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control. Más información en nuestra política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal, incluida información sobre acceso, conservación, rectificación, eliminación, seguridad y otros temas.