Apartados


4. Cadenas de texto

Nivel de dificultad:3 sobre 5

``Te digo esto porque eres uno de mis amigos,
¡Mi vocabulario comienza donde el tuyo termina!''
--Dr. Seuss, ¡On beyond Zebra!

4.1 Temas aburridos que debes conocer antes de la inmersión

¿Sabías que la gente de Bougainville tiene el alfabeto más pequeño del mundo? El alfabeto Rotokas está compuesto únicamente por 12 letras: A, E, G, I, K, O, P, R, S, T, U y V. En el otro lado del espectro los lenguajes como el Chino, Japonés y Koreano tienen miles de caracteres. El inglés, desde luego, tiene 26 letras --52 si cuentas las mayúsculas y minúsculas de forma separada-- más un puñado de símbolos de puntuación !@#$%&?.

Cuando las personas hablan sobre ``texto'' piensan en ``caracteres y símbolos en la pantalla del ordenador''. Pero los ordenadores no conocen ni símbolos ni caracteres, conocen bits y bytes. Cada elemento textual que ves en la pantalla está almacenado con una codificación de caracteres particular. Explicándolo de manera informal, la codificación de caracteres proporciona una conversión entre lo que ves en la pantalla y lo que el ordenador realmente almacena en memoria o en disco. Existen muchas codificaciones de caracteres diferentes, algunas optimizadas para determinados lenguajes como el ruso, el chino o el inglés, y otras que se pueden utilizar para diferentes lenguajes.

En realidad es más complicado. Muchos caracteres son comunes a diferentes codificaciones, pero cada codificación puede utilizar una secuencia de bytes diferente para almacenar esos caracteres en memoria o disco. Puedes imaginarte que una codificación de caracteres es como una especia de clave de desencriptado. Cuando tengas una secuencia de bytes --un fichero, una página web o cualquier otra cosa-- y se considere que esos bytes representan ``texto'', necesitas conocer en qué codificación de caracteres se encuentra para poder decodificar los bytes y conocer a qué caracteres representan. Si tienes una clave de decodificación equivocada o no dispones de ninguna, la decodificación no será posible o será errónea (si se usa una decodificación equivocada), y el resultado será un texto sin sentido.

Todo lo que que pensabas que sabías sobre las cadenas de texto es erróneo.

Seguramente habrás visto a veces paginas web con extraños caracteres de interrogación o similar, en donde esperabas algún carácter como el apóstrofo o vocales acentuadas. Esto suele indicar que el autor de la página no declaró correctamente la codificación de caracteres que utilizó por lo que tu navegador la tiene que adivinar y el resultado es una mezcla de caracteres esperados e inesperados. En inglés esto es simplemente desconcertante, pero en otros lenguajes el resultado puede ser completamente ilegible.

Existen tablas de codificación de caracteres para cada uno de los lenguajes importantes del mundo. Puesto que cada lenguaje es diferente, y la memoria y el espacio en disco ha sido caro históricamente, cada tabla de codificación de caracteres está optimizada para un lenguaje en particular. Lo que quiero decir con esto es que cada una de las codificaciones usa los mismos números (0 - 255) para representar los caracteres de un lenguaje determinado. Por ejemplo, posiblemente estés familiarizado con la codificación ASCII, que almacena los caracteres del inglés como números que van del 0 al 127 (65 es la ``A'', 97 es la ``a'', etc). El inglés es un alfabeto muy simple, por lo que puede expresarse con menos de 128 números. Para aquellos que sepan contar en base 2, eso significa 7 bits de los 8 de un byte.

Algunos lenguajes de Europa como el francés, español y alemán necesitan más letras que el inglés. O, para ser más precisos, tienen letras que se combinan con diversas marcas diacríticas, como el carácter ñ del español. La tabla de codificación de caracteres más común para estos lenguajes es CP-1252, que también es conocida como windows-1252 porque se utiliza ampliamente en el sistema operativo Microsoft Windows. La codificación CP-1252 comparte con ASCII los primeros 128 caracteres (0-127), pero luego se extiende en el rango de 128 a 255 para los caracteres restantes (241 es la ``ñ'', 252 es la ``ü'', etc). Continúa siendo una tabla de codificación de un único byte. El valor mayor, 255, aún cabe en un byte.

Además existen lenguajes como el chino, japonés y coreano, que tienen tantos caracteres que requieren tablas de codificación de caracteres multibyte. Esto significa que cada ``carácter'' se representa como un número de dos bytes lo que abarca del 0 al 65535. Pero las codificaciones multibyte también tienen el mismo problema que las diferentes codificaciones de un único byte: cada una de ellas puede utilizar el mismo número para expresar un carácter diferente. La única diferencia entre ellas es que el rango de caracteres disponible es mayor en las codificaciones multibyte.

Esto no suponía demasiado problema en un mundo desconectado, en donde ``texto'' era algo que tecleabas para ti y ocasionalmente imprimías. No existía mucho ``texto plano''. El código fuente era ASCII y todo el mundo usaba procesadores de textos que definían su propio formato que tenían en cuenta la información de codificación de caracteres junto con la información de estilo, etc. La gente leía estos documentos con el mismo programa procesador de texto que el autor original, por lo que todo funcionaba, más o menos.

Ahora piensa en la aparición de las redes globales y en el correo y la web. Mucho ``texto plano'' anda suelto por el mundo, se crea en un ordenador, se transmite a un segundo y se muestra en un tercero. Los ordenadores únicamente distinguen números, pero los números pueden significar cosas diferentes. ¡Oh no! ¿Qué hacer? Bien, los sistemas tuvieron que diseñarse para transportar la información de codificación junto con el ``texto plano''. Recuerda, se trata de las claves de descodificación que mapean los números entendidos por el ordenador a caracteres legibles por personas. Una clave de descodificación perdida da lugar a texto ilegible.

Ahora piensa en intentar almacenar diversos documentos de texto en el mismo lugar, como en una tabla de una misma base de datos que almacena todo el correo electrónico que hayas recibido. Aún necesitas almacenar la codificación de caracteres junto con cada correo electrónico para que se pueda leer apropiadamente. ¿Parece difícil? Prueba a buscar en tu base de datos de correos, eso significa convertir entre múltiples tablas de codificación de caracteres sobre la marcha. ¿No suena divertido?.

Piensa ahora en la posibilidad de documentos multilíngües, en donde aparecen caracteres en diferentes lenguajes4.1. Y, por supuesto, querrás buscar el contenido de esos documentos.

Ahora llora un rato, porque todo lo que creías conocer sobre las cadenas de texto es erróneo, y no existe algo así como el ``texto plano''.

4.2 Unicode

Entra en Unicode.

Unicode es un sistema diseñado para representar todos los caracteres de todos los lenguajes. Representa cada letra, carácter o ideograma como un número de cuatro bytes. Cada número representa a un único carácter que se use en al menos uno de los lenguajes del mundo (No se usan todos los números, pero se usan más de 65535 por lo que no es suficiente utilizar dos bytes). Los caracteres que se utilizan en diferentes lenguajes tienen el mismo número generalmente, a menos que exista una buena razón etimológica para que no sea así. De todos modos hay exactamente un número por cada carácter y un carácter por número. De esta forma, cada número siempre significa una única cosa. No existen ``modos'' que rastrear, U+0041 siempre corresponde a ``A'', incluso si tu lenguaje no usa tal símbolo.

A primera vista parece una gran idea, una tabla de codificación de caracteres para gobernarlos a todos. Múltiples lenguajes por documento, no más ``cambios de modo'' para conmutar entre tablas de codificación en medio de un documento. Pero existe una pregunta obvia. ¿Cuatro bytes? ¿Para cada carácter? Parece un gasto inútil la mayor parte de las ocasiones, especialmente para idiomas como el inglés o el español, que necesitan menos de un byte (256 números) para expresar cada uno de los caracteres posibles. De hecho, es un desperdicio de espacio incluso para los lenguajes basados en ideogramas como el chino, que nunca necesitan más de dos caracteres para cada carácter.

Existe una tabla de codificación Unicode que utiliza cuatro bytes por cada carácter. Se denomina UTF-32 porque 32 bits es igual a 4 bytes. UTF-32 es una codificación directa; toma cada carácter Unicode (un número de 4 bytes) y representa al carácter con ese mismo número. Esto tiene varias ventajas siendo la más importante que puedes encontrar el ``enésimo'' carácter de una cadena en un tiempo constante ya que se encuentra en a partir del byte 4*n. También tiene varios inconvenientes, siendo el más obvio que necesita cuatro bytes para almacenar cada carácter.

Incluso aunque existen muchos caracteres Unicode, resulta que la mayoría de la gente nunca usará nada más allá de los primeros 65535. Por eso existe otra codificación Unicode denominada UTF-16 (16 bits son 2 bytes) que codifica cada uno de los caracteres de 0 a 65535 como dos bytes. Además utiliza algunos ``trucos sucios'' por si necesitas representar aquellos caracteres que se usan raramente y que están más allá del 65535. La ventaja más obvia es que esta tabla de codificación de caracteres es el doble de eficiente que la de UTF-32 puesto que cada carácter requiere únicamente dos bytes para almacenarse, en lugar de cuatro bytes. Aún se puede encontrar fácilmente el enésimo carácter de una cadena en un tiempo constante, siempre que se asuma que no existen caracteres especiales de los que están por encima de 65535. Lo que suele ser una buena asunción... ¡hasta el momento en que no lo es!

También existen algunos inconvenientes no tan obvios tanto en UTF-32 y UTF-8. Los ordenadores de sistemas diferentes suelen almacenar los bytes de diferentes formas. Esto significa que el carácter U+4E2D podría almacenarse en UTF-16 bien como 4E 2D o como 2D 4E, dependiendo de que el sistema sea ``big-endian'' o ``little-endian''4.2 (para UTF-32 existen más posibilidades de ordenación de los bytes). Mientras tus documentos no dejen tu ordenador estás seguro --las diferentes aplicaciones del mismo ordenador utilizarán la misma ordenación de bytes. Pero en el momento que transfieras los documentos entre sistemas, tal vez a través de Internet, vas a necesitar una forma de indicar el orden en el que se han almacenado los bytes. De otra forma el sistema que recibe los datos no tiene forma de saber si la secuencia de dos bytes 4E 2D significa U+4E2D o U+2D4E.

Para resolver este problema, las codificaciones multibyte de Unicode definen el ``Byte Orden Mark'' (BOM)4.3, que es un carácter especial no imprimible que se puede incluir al comienzo de tu documento para indica qué ordenación de bytes tiene el documento. Para UTF-16 la marca de ordenación de bytes es U+FEFF, por lo que si recibes un documento UTF-16 que comienza con los bytes FF FE, ya sabes en qué forma vienen ordenados los bytes; si comienza con FE FF sabes que el orden es el contrario.

Aún así, UTF-16 no es exactamente el ideal, especialmente si la mayor parte de los caracteres que utilizas son ASCII. Si lo piensas, incluso una página web china contendrá muchos caracteres ASCII --todos los elementos y atributos que rodean a los caracteres imprimibles chinos. Poder encontrar el ``enésimo'' carácter está bien, pero existe el problema de los caracteres que requieren más de dos bytes, lo que significa que no puedes garantizar que todos los caracteres ocupan exactamente dos bytes, por lo que en la práctica no puedes encontrar el carácter de la posición ``enésima'' en un tiempo constante a no ser que mantengas un índice separado. Y muchacho, te aseguro que hay mucho texto ASCII por el mundo...

Otras personas valoraron estas preguntas y plantearon una solución:

UTF-8

UTF-8 es un sistema de codificación de longitud variable para Unicode. Esto significa que los caracteres pueden utilizar diferente número de bytes. Para los caracteres ASCII utiliza un único byte por carácter. De hecho, utiliza exactamente los mismos bytes que ASCII por lo que los 128 primeros caracteres son indistinguibles. Los caracteres ``latinos extendidos'' como la ñ o la ö utilizan dos bytes4.4. Los caracteres chinos utilizan tres bytes, y los caracteres más ``raros'' utilizan cuatro.

Desventajas: debido a que los caracteres pueden ocupar un número diferente de bytes, encontrar el carácter de la posición ``enésima'' es una operación de orden de complejidad O(n) --lo que significa que cuanto más larga sea la cadena, más tiempo lleva encontrar un carácter específico. Asimismo, hay que andar codificando y decodificando entre bytes y caracteres.

Ventajas: se trata de una codificación supereficiente de los caracteres ASCII. No es peor que UTF-16 para los caracteres latinos extendidos, y es mejor que UTF-32 para los caracteres chinos. También (aquí tendrás que confiar en mi, porque no te voy a mostrar la matemática involucrada), debido a la naturaleza exacta de la manipulación de los bits, no existen problemas de ordenación de bits. Un documento codificado en UTF-8 siempre contiene la misma cadena de bytes sea cual sea el ordenador y sistema operativo.

4.3 Inmersión

En Python 3 todas las cadenas de texto son secuencias de caracteres Unicode. En Python 3 no existen cadenas codificadas en UTF-8 o en CP-1252. No es correcto decir ¿Es esta cadena una cadena codificada en UTF-8?. UTF-8 es una forma de codificar caracteres en una secuencia de bytes. Si quieres convertir una cadena de caracteres en una secuencia de bytes en una codificación de caracteres particular, Python 3 puede ayudarte con ello. Si quieres tomar una secuencia de bytes y convertirla en una cadena de texto, también te puede ayudar Python 3. Los bytes no son caracteres, los bytes son bytes. Los caracteres son una abstracción. Una cadena es una secuencia de esas abstracciones.

»> s = '快乐 Python'
»> len(s)
9
»> s[0]
'快'
»> s + ' 3'
'快乐 Python 3'

  1. Línea 1: Para crear una cadena de texto debes utilizar las comillas para delimitarla. Se pueden utilizar comillas simples (') o dobles (").

  2. Línea 2: La función interna len() devuelve la longitud de la cadena, el número de caracteres. Esta función es la misma que utilizas para conocer la longitud de una lista, tupla, conjunto o diccionario. Una cadena es como una tupla de caracteres.

  3. Línea 4: De la misma forma que puedes obtener elementos individuales de una lista, puedes obtener caracteres individuales de una cadena mediante la notación de índices.

  4. Línea 6: Como con las listas y tuplas, puedes concatenar las cadenas utilizando el operador +.

4.4 Formatear cadenas

Las cadenas de texto se pueden definir utilizando comillas simples o dobles.

Vamos a echarle otro vistazo a parahumanos.py:

# parahumanos.py

SUFIJOS = 1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']

def tamanyo_aproximado(tamanyo, un_kilobyte_es_1024_bytes=True): '''Convierte un tamaño de fichero en formato legible por personas

Argumentos/parámetros: tamanyo - tamaño de fichero en bytes un_kilobyte_es_1024_bytes - si True (por defecto), usa múltiplos de 1024 si False, usa múltiplos de 1000

retorna: string

''' if tamanyo < 0: raise ValueError('el número debe ser no negativo')

multiplo = 1024 if un_kilobyte_es_1024_bytes else 1000 for sufijo in SUFIJOS[multiplo]: tamanyo /= multiplo if tamanyo < multiplo: return '0:.1f 1'.format(tamanyo, sufijo)

raise ValueError('número demasiado grande')

if __name__ == '__main__': print(tamanyo_aproximado(1000000000000, False)) print(tamanyo_aproximado(1000000000000))

  1. Línea 3: 'KB', 'MB', 'GB', ... son cadenas.

  2. Línea 8: Las cadenas de documentación (docstrings) son cadenas de texto. Como se expanden más allá de una línea se utilizan tres comillas al comienzo y al final para delimitarlas.

  3. Línea 18: Estas comillas triples finalizan la cadena de documentación de esta función.

  4. Línea 20: Otra cadena que se pasa como parámetro a la excepción con el fin de que sea legible por una persona.

  5. Línea 26: Esto es... ¡Uff! ¿Qué car.. es esto?

Python 3 permite formatear valores en cadenas de texto. Aunque este sistema permite expresiones muy complejas, su uso más básico consiste en insertar un valor en una cadena de texto en el lugar definido por un ``marcador''.

»> usuario = 'mark'
»> clave = 'PapayaWhip'
»> "La clave de 0 es 1".format(usuario, clave)
"La clave de mark es PapayaWhip"

  1. Línea 2: Realmente mi clave no es PapayaWhip.

  2. Línea 3: Aquí hay mucho que contar. Primero, se observa una llamada a un método sobre una cadena de texto. Las cadenas de texto son objetos, y los objetos tienen métodos, como ya sabes. Segundo, la expresión completa se evalúa a una cadena. Tercero, {0} y {1} son campos de reemplazo, que se sustituyen con los parámetros que se pasen al método format().

4.4.1 Nombres de campos compuestos

En el ejemplo anterior se muestra el ejemplo más simple, aquél en el que los campos de reemplazo son números enteros. Los campos de reemplazo enteros se tratan como índices posicionales en la lista de parámetros del método format(). Eso significa que el {0} se reemplaza por el primer parámetro (usuario en este caso), {1} se reemplaza por el segundo (clave en este caso), etc. Puedes utilizar tantos campos de reemplazo posicional como parámetros se pasen en el método format(). Pero los campos de reemplazo permiten mucha más funcionalidad.

»> import parahumanos
»> mis_sufijos = parahumanos.SUFIJOS[1000]
»> mis_sufijos
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
»> '10000[0] = 10[1]'.format(mis_sufijos)
'1000KB = 1MB'

  1. Línea 2: En lugar de ejecutar una función del módulo parahumanos únicamente estás capturando una de las estructuras de datos que define; la lista de sufijos que representan las potencias de 1000.

  2. Línea 5: Esta línea parece complicada, pero no lo es. {0} representa el primer parámetro del método format(), mis_sufijos. Por eso {0[0]} se refiere al primer elemento de la lista que esté definida como el primer parámetro del método format(): 'KB'. Mientras que {0[1]} se refiere al segundo elemento de la misma lista: 'MB'. Todo lo que está fuera de las llaves --incluido el 1000, los signos de igual, y los espacios-- quedan sin tocar. El resultado final es: '1000KB = 1MB'.

El {0} se reemplaza por el primer parámetro, {1} se reemplaza por el segundo.

Lo que este ejemplo te enseña es que los especificadores de formato pueden utilizarse para acceder a los elementos y propiedades de las estructuras de datos utilizando (casi) sintaxis de Python. Esto se denomina nombres de campos compuestos. Estos son los nombres de campos que funcionan:

Solamente para despejar tu cerebro te muestro aquí un ejemplo que combina todo lo anterior.

»> import parahumanos
»> import sys
»> '1MB = 10000.modules[parahumanos].SUFIJOS[1000][0]'.format(sys)
'1MB = 1000KB'

Así es como funciona:

4.4.2 Especificaciones de formato

¡Pero espera! ¡Hay mas! Vamos a echar otro vistazo a la línea de código más extraña de parahumanos.py:

if tamanyo < multiplo:
    return '0:.1f 1'.format(tamanyo, sufijo)

Como ya sabemos, {1} se sustituye por el segundo parámetro sufijo del método format(). Pero ¿qué es {0:.1f}? Tiene dos partes, {0} que ya conoces, y :.f que no conoces. La segunda parte (desde los dos puntos hasta la letra f) define el especificador de forma que afina cómo debe sustituirse la variable al formatearla.

Los especificadores de formato te permiten indicar cómo se debe efectuar la sustitución del texto, como sucede con la función printf() en el lenguaje C. Puedes añadir ceros o espacios de relleno delante del número, alinear cadenas, controlar la precisión de decimales o convertir el número a hexadecimal.

Dentro del campo de sustitución se utiliza el símbolo de dos puntos (:) para marcar el comienzo del especificador de formato. El especificador de formato .1 significa que se ``redondee a la décima más próxima'' (que se muestre únicamente un dígito después del punto decimal). El especificador ``f'' indica que el número debe mostrarse en formato punto fijo (por oposición a la notación exponencial u otra representación de un número). Si la variable tamanyo vale 698.24 y la variable sufijo vale ``GB'' la cadena formateada resultante es ``698.2 GB'', porque 698.24 se redondea con un solo dígito después del punto decimal.

»> '0:.1f 1'.format(698.24, 'GB')
'698.2 GB'

Para conocer los detalles exactos de los especificadores de formato puedes consultar el apartado Mini-lenguaje de especificación de formato4.5 de la documentación oficial de Python 3.

4.5 Otros métodos habituales de manipulación de cadenas

Además de formatearlas, es posible hacer muchas otras cosas de utilidad con las cadenas de texto.

»> s = '''Los archivos terminados son el re-
... sultado de años de estudio cientí-
... fico combinados con la 
... experiencia de años.'''
»> s.splitlines()
['Los archivos terminados son el re-', 
 'sultado de años de estudio cientí-', 
 'fico combinados con la ', 
 'experiencia de años.']
»> print(s.lower())
los archivos terminados son el re-
sultado de años de estudio cientí-
fico combinados con la 
experiencia de años.
»> s.lower().count('l')
4

  1. Línea 1: Puedes introducir cadenas multilínea en la consola interactiva de Python. Cuando inicias una cadena de texto multilínea debes pulsar la tecla INTRO para continuar en la siguiente línea. Al teclear las triples comillas del final, se cierra la cadena de texto y el siguiente INTRO que pulses ejecutará la sentencia (en este caso asignará la cadena a la variable s).

  2. Línea 5: El método splitlines() toma una cadena multilínea y devuelve una lista de cadenas de texto, una por cada línea que contuviese la cadena original. Observa que las líneas no incluyen los retornos de carro o finales de línea que tuviese la cadena original.

  3. Línea 10: El método lower() convierte toda la cadena de texto a minúsculas (El método upper() convertiría toda la cadena de texto a mayúsculas).

  4. Línea 15: El método count() cuenta el número de veces que aparece una subcadena en la cadena de texto. ¡Sí! Hay 4 caracteres ``l'' en la cadena.

Pongamos un caso muy común. Supón que tienes una cadena de texto en forma de parejas clave-valor, clave1=valor1&clave2=valor2, y quieres dividirla y crear un diccionario de la forma {clave1: valor1, clave2: valor2}.

»> consulta = 'usuario=pilgrim&basededatos=master&clave=PapayaWhip'
»> una_lista = consulta.split('&')
»> una_lista
['usuario=pilgrim', 'basededatos=master', 'clave=PapayaWhip']
»> una_lista_de_listas = [v.split('=', 1) for v in una_lista]
»> una_lista_de_listas
[['usuario', 'pilgrim'], ['basededatos', 'master'],
 ['clave', 'PapayaWhip']]
»> a_dict = dict(una_lista_de_listas)
»> a_dict
'clave': 'PapayaWhip', 'usuario': 'pilgrim', 'basededatos': 'master'

  1. Línea 2: El método split() toma un parámetro, un delimitador, y divide la cadena en una lista de cadenas basándose en el delimitador proporcionado. En este ejemplo, el delimitador es el carácter &.

  2. Línea 5: Ahora tenemos una lista de cadenas, cada una de ellas con una clave seguida del símbolo = y de un valor. Podemos utilizar las listas por comprensión para iterar sobre esta lista y dividir cada una de estas cadenas de texto en dos cadenas utilizando el método split pasándole un segundo parámetro que indica que únicamente utilice la primera ocurrencia del carácter separador (En teoría una cadena podría tener más de un símbolo igual si el valor, a su vez, contiene también el símbolo igual, por ejemplo: 'clave=valor=cero', con lo que 'clave=valor=cero'.split('=') daría como resultado ['clave', 'valor', 'cero']).

  3. Línea 9: Finalmente, Python puede convertir esa lista de listas en un diccionario con solo pasarla como parámetro a la función dict().

El ejemplo anterior, explica un caso que se parece a lo que habría que hacer para reconocer los parámetros de una URL. Pero en la vida real, el reconocimiento de los parámetros de una URL es más complejo. Si vas a tener que reconocer los parámetros que recibes mediante una URL utiliza la función de la librería urlib.parse denominada parse_qs()4.6, que reconoce los casos más complejos.

4.5.1 Troceado de cadenas

Cuando ya has definido una cadena puedes recuperar cualquier parte de ella creando una nueva cadena de texto. A esto se denomina troceado/particionado de cadenas4.7. Esto funciona de forma idéntica a como funciona para las listas, lo que tiene sentido, porque las cadenas de texto no son más que cadenas de caracteres.

»> una_cadena = 'Mi vocabulario comienza donde el tuyo termina'
»> una_cadena[3:14]
'vocabulario'
»> una_cadena[3:-3]
'vocabulario comienza donde el tuyo term'
»> una_cadena[0:2]
'Mi'
»> una_cadena[:23]
'Mi vocabulario comienza'
»> una_cadena[23:]
' donde el tuyo termina'

  1. Línea 2: Puedes recuperar una parte de la cadena de texto, una parte de ella, especificando dos índices. El valor de retorno es una nueva cadena que comienza en el primer índice y termina en el elemento anterior al segundo índice.

  2. Línea 4: Como sucede con las listas, puedes utilizar índices negativos para seleccionar.

  3. Línea 6: Las cadenas también comienzan a contar en cero, por lo que una_cadena[0:2] devuelve los dos primeros elementos de la cadena, comenzando en la posición una_cadena[0] hasta la posición una_cadena[2], pero sin incluirla.

  4. Línea 8: Si el índice de la parte izquierda vale 0 puedes omitirlo. De este modo, una_cadena[:23] es lo mismo que una_cadena[0:18]. Ya que en ausencia del primer índice se asume el número 0.

  5. Línea 10: De forma similar, si el índice de la parte derecha de la cadena coincide con la longitud de la cadena, puedes omitirlo. Así que una_cadena[23:] es lo mismo que una_cadena[23:45] al medir esta cadena 45 caracteres. Como ves, existe una estupenda simetría en esto, en esta cadena de 45 caracteres una_cadena[0:23] devuelve los primeros 23 caracteres, y una_cadena[23:] devuelve todo lo demás, salvo los 23 caracteres iniciales. De hecho una_cadena[:n] siempre retornará los primeros n caracteres, y una_cadena[n:] retornará el resto, independientemente de la longitud que tenga la cadena.

4.6 Cadenas de texto y Bytes

Los bytes son bytes; los caracteres son una abstracción. A una secuencia inmutable de caracteres Unicode se le llama cadena de texto. Una secuencia inmutable de números entre el 0 y el 255 es un objeto que se denomina bytes.

»> by = b'abcd65'
»> by
b'abcde'
»> type(by)
<class 'bytes'>
»> len(by)
5
»> by += b''
»> by
b'abcde'
»> len(by)
6
»> by[0]
97
»> by[0] = 102
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment

  1. Línea 1: Para definir un objeto bytes se usa la sintaxis de ``literal de bytes'' que es b''. Cada byte dentro del literal de bytes se interpreta como un carácter ASCII o un carácter codificado en número hexadecimal desde
    x00
    a
    xFF
    (0-255).

  2. Línea 4: El tipo de un objeto bytes es bytes.

  3. Línea 6: Como sucede con las listas y cadenas, puedes conocer la longitud de un objeto bytes utilizando la función interna len().

  4. Línea 8: Como sucede con las listas y cadenas, puedes utilizar el operador + para concatenar objetos bytes. El resultado es un nuevo objeto bytes.

  5. Línea 11: Concatenar un objeto bytes de 5 bytes con uno de 1 byte da como resultado un objeto bytes de 6 bytes.

  6. Línea 13: Como sucede con las listas y cadenas, puedes utilizar la notación de índices para obtener bytes individuales del objeto bytes. Los elementos individuales de una cadena son de tipo cadena; los elementos individuales de un objeto bytes son números enteros. Específicamente, enteros entre 0 y 255.

  7. Línea 15: Un objeto bytes es inmutable; no puedes asignar bytes individuales. Si necesitas modificar bytes individuales de un objeto bytes, es necesario particionar y concatenar para crear un nuevo objeto bytes que contenga los elementos deseados. La alternativa es convertir el objeto bytes en un bytearray que sí permite modificación.

»> by = b'abcd65'
»> barr = bytearray(by)
»> barr
bytearray(b'abcde')
»> len(barr)
5
»> barr[0] = 102
»> barr
bytearray(b'fbcde')

  1. Línea 2: Para convertir un objeto bytes en un objeto modificable de tipo bytearray puedes utilizar la función interna bytearray().

  2. Línea 5: Todos los métodos y operaciones que existen en el objeto bytes también están disponibles en el objeto bytearray.

  3. Línea 7: Una de las diferencias es que al objeto bytearray es posible modificarle bytes individuales utilizando la notación de índice. El valor que se puede asignar debe estar entre 0 y 255.

Algo que no se puede hacer es mezclar bytes y cadenas.

»> by = b'd'
»> s = 'abcde'
»> by + s
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
»> s.count(by)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
»> s.count(by.decode('ascii'))
1

  1. Línea 3: No puedes concatenar bytes y cadenas. Son dos tipos de dato diferentes.

  2. Línea 7: No puedes contar las veces que aparece una secuencia de bytes en una cadena, porque no existen bytes en una cadena. Una cadena es una secuencia de caracteres. Tal vez lo que querías contar era las veces que aparece la cadena que obtendrías después de haber decodificado la secuencia de bytes interpretándola a partir de una tabla de codificación de caracteres particular. Si es así, debes decirlo explícitamente. Python 3 no convertirá implícitamente bytes en cadenas o cadenas en bytes.

  3. Línea 11: Por una sorprendente coincidencia esta línea de código dice ``cuenta las ocurrencias de la cadena que se obtiene después de decodificar la secuencia de bytes en esta tabla de caracteres particular (ASCII)''.

Y de este modo has llegado a la relación que existe entre las cadenas de texto y los bytes: los objetos bytes tienen un método decode() que toma como parámetro una tabla de codificación de caracteres y retorna una cadena. Y las cadenas de texto tienen un método denominado encode() que toma una tabla de codificación de caracteres y retorna un objeto bytes. En el ejemplo anterior, la decodificación fue relativamente directa --convertir una secuencia de bytes que estaba en la codificación de caracteres ASCII en una cadena de texto. Pero el mismo proceso funciona con cualquier tabla de codificación de caracteres siempre que dicha tabla soporte los caracteres existentes en la cadena --incluso con codificaciones heredadas (previas a Unicode).

»> s = '快乐 Python'
»> len(s)
9
»> by = s.encode('utf-8')
»> by
b'54990 Python'
»> len(by)
13
»> by = s.encode('gb18030')
»> by
b'06 Python'
»> len(by)
11
»> by = s.encode('utf-16')
»> by
b'_PN 00P00y00t00h00o00n00'
»> len(by)
20
»> vuelta = by.decode('utf-16')
'快乐 Python'
»> vuelta == s
True

  1. Línea 1: Esto es una cadena de texto, tiene 9 caracteres.

  2. Línea 4: El resultado de codificar la cadena en UTF-8 es un objeto bytes. Tiene 13 bytes.

  3. Línea 9: El resultado de codificar la cadena en GB18030 es un objeto bytes de 11 bytes.

  4. Línea 14: El resultado de codificar la cadena en UTF-16 es un objeto bytes de 20 bytes.

  5. Línea 19: Al decodificar el objeto bytes utilizando la codificación adecuada (la misma que se usó al codificarlo) se obtiene una cadena de texto. En este caso tiene 9 caracteres. Como se puede ver, es una cadena idéntica a la original.

4.7 Postdata: Codificación de caracteres del código fuente de Python

Python 3 asume que el código fuente --cada fichero .py-- está codificado en UTF-8.

En Python 2, la codificación de caracteres por defecto de los ficheros .py era ASCII. En Python 3, la codificación por defecto de los ficheros es UTF-8

Si quisieras utilizar una codificación de caracteres diferente en el fichero con el código fuente, puedes incluir una declaración de codificación de caracteres en la primera línea cada fichero. La siguiente declaración define que el fichero se encuentra en una codificación windows-1252

# -*- coding: windows-1252 -*-

Técnicamente la indicación de la codificación de caracteres puede estar en la segunda línea si la primera línea es una declaración de lanzador de ejecución del estilo de UNIX.

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

Para disponer de más información consulta la propuesta de mejora de Python PEP 2634.8.

4.8 Lecturas recomendadas

Sobre Unicode en Python:

Sobre Unicode en general:

Sobre codificación de caracteres en otros formatos:

Sobre cadenas y formateo de cadenas:

José Miguel González Aguilera 2016-08-18