Previous Up Next

Capítulo ‍10 Tuplas

10.1 Las tuplas son inmutables

Una tupla1 es una secuencia de valores muy parecida a una lista. Los valores almacenados en una tupla pueden ser de cualquier tipo, y están indexados por enteros. La diferencia más importante es que las tuplas son inmutables. Las tuplas además son comparables y dispersables (hashables), de modo que las listas de tuplas se pueden ordenar y también es posible usar tuplas como valores para las claves en los diccionarios de Python.

Sintácticamente, una tupla es una lista de valores separados por comas:

>>> t = 'a', 'b', 'c', 'd', 'e'

A pesar de que no es necesario, resulta corriente encerrar las tuplas entre paréntesis, lo que ayuda a identificarlas rápidamente dentro del código en Python.

>>> t = ('a', 'b', 'c', 'd', 'e')

Para crear una tupla con un único elemento, es necesario incluir una coma al final:

>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>

Sin la coma, Python trata ('a') como una expresión con una cadena dentro de un paréntesis, que evalúa como de tipo “string”:

>>> t2 = ('a')
>>> type(t2)
<type 'str'>

Otro modo de construir una tupla es usar la función interna tuple. Sin argumentos, crea una tupla vacía:

>>> t = tuple()
>>> print t
()

Si el argumento es una secuencia (cadena, lista o tupla), el resultado de la llamada a tuple es una tupla con los elementos de la secuencia:

>>> t = tuple('altramuces')
>>> print t
('a','l', 't', 'r', 'a', 'm', 'u', 'c', 'e', 's')

Dado que tuple es el nombre de un constructor, debe evitarse el utilizarlo como nombre de variable.

La mayoría de los operadores de listas funcionan también con tuplas. El operador corchete indexa un elemento:

>>> t = ('a', 'b', 'c', 'd', 'e')
>>> print t[0]
'a'

Y el operador de rebanada (slice) selecciona un rango de elementos.

>>> print t[1:3]
('b', 'c')

Pero si se intenta modificar uno de los elementos de la tupla, se obtiene un error:

>>> t[0] = 'A'
TypeError: object doesn't support item assignment

No se pueden modificar los elementos de una tupla, pero se puede reemplazar una tupla con otra:

>>> t = ('A',) + t[1:]
>>> print t
('A', 'b', 'c', 'd', 'e')

10.2 Comparación de tuplas

Los operadores de comparación funcionan también con las tuplas y otras secuencias. Python comienza comparando el primer elemento de cada secuencia. Si es igual en ambas, pasa al siguiente elemento, y así sucesivamente, hasta que encuentra uno que es diferente. A partir de ese momento, los elementos siguientes ya no son tenidos en cuenta (aunque sean muy grandes).

>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True

La función sort funciona del mismo modo. En principio ordena por el primer elemento, pero en caso de que haya dos iguales, usa el segundo, y así sucesivamente.

Esta característica se presta al uso de un diseño llamado DSU, que

Decorate
(Decora) una secuencia, construyendo una lista de tuplas con uno o más índices ordenados precediendo los elementos de dicha secuencia,
Sort
(Ordena) la lista de tuplas usando la función interna de Python sort, y
Undecorate
(Quita la decoración), extrayendo los elementos ordenados de la secuencia.

Por ejemplo, supón que tienes una lista de palabras y que quieres ordenarlas de más larga a más corta:

txt = 'Pero qué luz se deja ver allí'
palabras = txt.split()
t = list()
for palabra in palabras:
   t.append((len(palabra), palabra))

t.sort(reverse=True)

res = list()
for longitud, palabra in t:
    res.append(palabra)

print res

El primer bucle crea una lista de tuplas, en la que cada tupla es la palabra precedida por su longitud.

sort compara el primer elemento (longitud), y sólo tiene en cuenta el segundo en caso de empate. El argumento clave reverse=True indica a sort que debe ir en orden decreciente.

El segundo bucle recorre la lista de tuplas y crea una lista de palabras en orden descendente según su longitud. Las palabras con cuatro caracteres, por ejemplo, son ordenadas en orden alfabético inverso, de modo que “deja” aparece antes que “allí” en esa lista.

La salida del programa es la siguiente:

['deja', 'allí', 'Pero', 'ver', 'qué', 'luz', 'se']

Por supuesto, la línea pierde mucho de su impacto poético cuando la convertimos en una lista de Python y la ordenamos en orden descendente según la longitud de sus palabras.

10.3 Asignación de tuplas

Una de las características sintácticas del lenguaje Python que resulta única es la capacidad de tener una tupla en el lado izquierdo de una sentencia de asignación. Esto permite asignar varias variables el mismo tiempo cuando tenemos una secuencia en el lado izquierdo.

En este ejemplo tenemos una lista de dos elementos (por lo que se trata de una secuencia), y asignamos los elementos primero y segundo de la secuencia a las variables x e y en una única sentencia.

>>> m = [ 'pásalo', 'bien' ]
>>> x, y = m
>>> x
'pásalo'
>>> y
'bien'
>>> 

No es magia, Python traduce aproximadamente la sintaxis de asignación de la tupla de este modo:2

>>> m = [ 'pásalo', 'bien' ]
>>> x = m[0]
>>> y = m[1]
>>> x
'pásalo'
>>> y
'bien'
>>> 

Estilísticamente, cuando usamos una tupla en el lado izquierdo de la sentencia de asignación, omitimos los paréntesis. Pero lo que se muestra a continuación es una sintaxis igualmente válida:

>>> m = [ 'pásalo', 'bien' ]
>>> (x, y) = m
>>> x
'pásalo'
>>> y
'bien'
>>> 

Una aplicación especialmente ingeniosa de asignación usando una tupla nos permite intercambiar los valores de dos variables en una única sentencia:

>>> a, b = b, a

Ambos lados de esta sentencia son tuplas, pero el lado izquierdo es una tupla de variables; el lado derecho es una tupla de expresiones. Cada valor en el lado derecho es asignado a su respectiva variable en el lado izquierdo. Todas las expresiones en el lado derecho son evaluadas antes de realizar ninguna asignación.

La cantidad de variables en el lado izquierdo y la cantidad de valores en el derecho debe ser la misma:

>>> a, b = 1, 2, 3
ValueError: too many values to unpack

Generalizando más, el lado derecho puede ser cualquier tipo de secuencia (cadena, lista o tupla). Por ejemplo, para dividir una dirección de e-mail en nombre de usuario y dominio, podrías escribir:

>>> dir = '[email protected]'
>>> nombreus, dominio = dir.split('@')

El valor de retorno de split es una lista con dos elementos; el primer elemento es asignado a nombreus, el segundo a dominio.

>>> print nombreus
monty
>>> print dominio
python.org

10.4 Diccionarios y tuplas

Los diccionarios tienen un método llamado items que devuelve una lista de tuplas, cada una de las cuales es una pareja clave-valor 3.

>>> d = {'a':10, 'b':1, 'c':22}
>>> t = d.items()
>>> print t
[('a', 10), ('c', 22), ('b', 1)]

Como sería de esperar en un diccionario, los elementos no tienen ningún orden en particular.

Sin embargo, dado que la lista de tuplas es una lista, y las tuplas son comparables, ahora podemos ordenar la lista de tuplas. Convertir un diccionario en una lista de tuplas es un método para obtener el contenido de un diccionario ordenado según sus claves:

>>> d = {'a':10, 'b':1, 'c':22}
>>> t = d.items()
>>> t
[('a', 10), ('c', 22), ('b', 1)]
>>> t.sort()
>>> t
[('a', 10), ('b', 1), ('c', 22)]

La nueva lista está ordenada alfabéticamente en orden ascendente según el valor de sus claves.

10.5 Asignación múltiple con diccionarios

La combinación de items, asignación en tupla y for, consigue un bonito diseño de código para recorrer las claves y valores de un diccionario en un único bucle:

for clave, valor in d.items():
    print valor, clave

Este bucle tiene dos variables de iteración, ya que items devuelve una lista de tuplas y clave, valor es una asignación en tupla, que itera sucesivamente a través de cada una de las parejas clave-valor del diccionario.

Para cada iteración a través del bucle, tanto clave como valor van pasando a la siguiente pareja clave-valor del diccionario (todavía en orden de dispersión).

La salida de este bucle es:

10 a
22 c
1 b

Otra vez obtenemos un orden de dispersión (es decir, ningún orden concreto).

Si combinamos estas dos técnicas, podemos imprimir el contenido de un diccionario ordenado por el valor almacenado en cada pareja clave-valor.

Para conseguirlo, primero creamos una lista de tuplas, donde cada tupla es (valor, clave). El método items nos dará una lista de tuplas (clave, valor)—pero esta vez queremos ordenar por valor, no por clave. Una vez que hayamos construido la lista con las tuplas clave-valor, resulta sencillo ordenar la lista en orden inverso e imprimir la nueva lista ordenada.

>>> d = {'a':10, 'b':1, 'c':22}
>>> l = list()
>>> for clave, valor in d.items() :
...     l.append( (valor, clave) )
... 
>>> l
[(10, 'a'), (22, 'c'), (1, 'b')]
>>> l.sort(reverse=True)
>>> l
[(22, 'c'), (10, 'a'), (1, 'b')]
>>> 

Al construir la lista de tuplas, hay que tener la precaución de colocar el valor como primer elemento de cada tupla, de modo que luego podamos ordenar la lista de tuplas y así obtener el contenido de nuestro diccionario ordenado por valor.

10.6 Las palabras más comunes

Volviendo a nuestro ejemplo anterior del texto de Romeo and Juliet Acto 2, Escena 2, podemos mejorar nuestro programa para hacer uso de esta técnica e imprimir las diez palabras más comunes en el texto, como vemos a continuación:

import string
manf = open('romeo-full.txt')
contadores = dict()
for linea in manf:
    linea = linea.translate(None, string.punctuation)
    linea = linea.lower()
    palabras = linea.split()
    for palabra in palabras:
        if palabra not in contadores:
            contadores[palabra] = 1
        else:
            contadores[palabra] += 1

# Ordenar el diccionario por valor
lst = list()
for clave, valor in contadores.items():
    lst.append( (valor, clave) )

lst.sort(reverse=True)

for clave, valor in lst[:10] :
    print clave, valor

La primera parte del programa, que lee el archivo y construye un diccionario que mapea cada palabra con las veces que se repite esa palabra en el documento, no ha cambiado. Pero en lugar de imprimir simplemente en pantalla contadores y terminar el programa, ahora construimos una lista de tuplas (valor, clave) y luego ordenamos la lista en orden inverso.

Dado que el valor va primero, se utilizará para las comparaciones. Si hay más de una tupla con el mismo valor, se tendrá en cuenta el segundo elemento (la clave), de modo que las tuplas cuyo valor sea el mismo serán además ordenadas alfabéticamente según su clave.

Al final escribimos un bonito bucle for que hace una iteración con asignación múltiple e imprime en pantalla las diez palabras más comunes, iterando a través de una rebanada de la lista (lst[:10]).

De modo que la salida al final tiene el aspecto que queríamos para nuestro análisis de frecuencia de palabras.

61 i
42 and
40 romeo
34 to
34 the
32 thou
32 juliet
30 that
29 my
24 thee

El hecho de que este complejo análisis y procesado de datos pueda ser realizado con un programa Python de 19 líneas sencillo de entender, es una de las razones por las que Python es una buena elección como lenguaje para explorar información.

10.7 Uso de tuplas como claves en diccionarios

Dado que las tuplas son dispersables (hashables) y las listas no, si queremos crear una clave compuesta para usar en un diccionario, deberemos usar una tupla como clave.

Usaríamos por ejemplo una clave compuesta si quisiésemos crear un directorio telefónico que mapease parejas apellido, nombre con números de teléfono. Asumiendo que hemos definido las variables apellido, nombre, y numero, podríamos escribir una sentencia de asignación de diccionario como la siguiente:

directorio[apellido,nombre] = numero

La expresión dentro de los corchetes es una tupla. Podríamos usar asignaciones mediante tuplas en un bucle for para recorrer este diccionario.

for apellido, nombre in directorio:
    print nombre, apellido, directorio[apellido, nombre]

Este bucle recorre las claves de directorio, que son tuplas. Asigna los elementos de cada tupla a apellido y nombre, luego imprime el nombre, apellido y número de teléfono correspondiente.

10.8 Secuencias: cadenas, listas, y tuplas—¡Dios mío!

Me he centrado en las listas y tuplas, pero casi todos los ejemplos de este capítulo funcionan también en listas de listas, tuplas de tuplas y tuplas de listas. Para evitar enumerar todas las combinaciones posibles, a veces resulta más sencillo hablar de secuencias de secuencias.

En muchos contextos, los diferentes tipos de secuencias (cadenas, listas, y tuplas) pueden intercambiarse. De modo que, ¿cuándo y por qué elegir uno u otro?

Para comenzar con lo más obvio, las cadenas están más limitadas que las demás secuencias, porque los elementos deben ser caracteres. También son inmutables. Si necesitas la capacidad de cambiar los caracteres en una cadena (en vez de crear una nueva), puede que lo más adecuado sea elegir una lista de caracteres.

Las listas se usan con más frecuencia que las tuplas, principalmente porque son mutables. Pero hay algunos casos donde es posible que prefieras usar las tuplas:

  1. En algunos contextos, como una sentencia return, resulta sintácticamente más simple crear una tupla que una lista. En otros contextos, es posible que prefieras una lista.
  2. Si quieres usar una secuencia como una clave en un diccionario, debes usar un tipo inmutable como una tupla o una cadena.
  3. Si estás pasando una secuencia como argumento de una función, el uso de tuplas reduce los comportamientos potencialmente indeseados debido a la creación de alias.

Dado que las tuplas son inmutables, no proporcionan métodos como sort y reverse, que modifican listas ya existentes. Sin embargo, Python proporciona las funciones integradas sorted y reversed, que toman una secuencia como parámetro y devuelven una secuencia nueva con los mismos elementos en un orden diferente.

10.9 Depuración

Las listas, diccionarios y tuplas son conocidas de forma genérica como estructuras de datos; en este capítulo estamos comenzando a ver estructuras de datos compuestas, como listas o tuplas, y diccionarios que contienen tuplas como claves y listas como valores. Las estructuras de datos compuestas son útiles, pero también resultan propensas a lo que yo llamo errores de modelado; es decir, errores causados cuando una estructura de datos tiene el tipo, tamaño o composición incorrecto, o tal vez al escribir una parte del código se nos olvidó cómo era el modelado de los datos y se introdujo un error.

Por ejemplo, si estás esperando una lista con un entero y te paso simplemente un entero sin más (no en una lista), no funcionará.

Cuando estés depurando un programa, y especialmente si estás trabajando en un fallo complicado, hay cuatro cosas que puedes probar:

lectura:
Examina tu código, léelo para ti, y comprueba si en realidad dice lo que querías que dijera.
ejecución:
Experimenta haciendo cambios y ejecutando versiones diferentes. A menudo, si muestras las cosas correctas en los lugares adecuados del programa el problema se convierte en obvio, pero otras veces tendrás que invertir algún tiempo construyendo ciertas estructuras.
rumiado:
¡Tómate tu tiempo para reflexionar! ¿De qué tipo de error se trata: sintáctico, de ejecución, semántico? ¿Qué información puedes obtener de los mensajes de error, o de la salida del programa? ¿Qué tipo de error podría causar el problema que estás viendo? ¿Qué fue lo último que cambiaste, antes de que el problema apareciera?
retirada:
En algunos casos, lo mejor que se puede hacer es dar marcha atrás, deshaciendo los últimos cambios, hasta llegar a un punto en que el programa funcione y tú seas capaz de entenderlo. A partir de ahí, puedes comenzar a reconstruirlo.

Los programadores novatos a veces se quedan atascados en una de estas actividades y olvidan las otras. Cada actividad cuenta con su propio tipo de fracaso.

Por ejemplo, leer tu código puede ayudarte si el problema es un error tipográfico, pero no si se trata de un concepto erróneo. Si no comprendes qué es lo que hace el programa, puedes leerlo 100 veces y nunca encontrarás el error, porque el error está en tu cabeza.

Hacer experimentos puede ayudar, especialmente si estás ejecutando pruebas pequeñas y sencillas. Pero si ejecutas experimentos sin pararte a pensar o leer tu código, puedes caer en el modelo que yo llamo “sistema de programación al azar”, que es el proceso de hacer cambios aleatorios hasta que el programa hace lo que tiene que hacer. No es necesario decir que este tipo de programación puede llevar mucho tiempo.

Debes de tomarte tu tiempo para reflexionar. La depuración es como una ciencia experimental. Debes tener al menos una hipótesis acerca de dónde está el problema. Si hay dos o más posibilidades, intenta pensar en una prueba que elimine una de ellas.

Tomarse un respiro ayuda a pensar. También hablar. Si explicas el problema a alguien más (o incluso a ti mismo), a veces encontrarás la respuesta antes de haber terminado de hacer la pregunta.

Pero incluso las mejores técnicas de depurado pueden fallar si hay demasiados errores, o si el código que se está intentando arreglar es demasiado grande y complicado. A veces la mejor opción es retirarse y simplificar el programa hasta tener algo que funcione y que se sea capaz de entender.

Los programadores novatos a menudo se muestran reacios a volver atrás, no pueden tolerar la idea de borrar ni una línea de código (incluso si está mal). Si eso te hace sentirte mejor, puedes copiar tu programa en otro archivo antes de empezar a eliminar cosas. Luego podrás volver a pegar los trozos poco a poco.

Encontrar un fallo difícil requiere leer, ejecutar, rumiar, y a veces, retirarse. Si te quedas atascado en una de estas actividades, intenta pasar a cualquiera de las otras.

10.10 Glosario

asignación en tupla:
Una asignación con una secuencia en el lado derecho y una tupla de variables en el izquierdo. Primero se evalúa el lado derecho y luego sus elementos son asignados a las variables de la izquierda.
comparable:
Un tipo en el cual un valor puede ser contrastado para ver si es mayor que, menor que, o igual que otro valor del mismo tipo. Los tipos que son comparables pueden ser puestos en una lista y ordenados.
estructura de datos:
Una colección de valores relacionados, a menudo organizados en listas, diccionarios, tuplas, etc.
DSU:
Abreviatura de “decorate-sort-undecorate (decorar-ordenar-quitar la decoración)”, un diseño que implica construir una lista de tuplas, ordenar, y extraer parte del resultado.
hashable (dispersable):
Un tipo que tiene una función de dispersión. Los tipos inmutables, como enteros, flotantes y cadenas son hashables (dispersables); los tipos mutables como listas y diccionarios no lo son.
dispersar:
La operación de tratar una secuencia como una lista de argumentos.
modelado (de una estructura de datos):
Un resumen del tipo, tamaño, y composición de una estructura de datos.
reunir:
La operación de montar una tupla con argumentos de longitud variable.
singleton:
Una lista (u otra secuencia) con un único elemento.
tupla:
Una secuencia inmutable de elementos.

10.11 Ejercicios

Ejercicio ‍1 Revisa el ejercicio 9.3, del tema anterior, de este modo: Lee y procesa las líneas “From” y extrae la dirección. Cuenta el número de mensajes de cada persona usando un diccionario. Después de que todos los datos hayan sido leídos, para mostrar la persona con más envíos, crea una lista de tuplas (contador, email) a partir del diccionario. Luego ordena la lista en orden inverso y muestra la persona que tiene más envíos.
Línea de ejemplo:
From [email protected] Sat Jan  5 09:14:16 2008

Introduzca un nombre de fichero: mbox-short.txt
[email protected] 5

Introduzca un nombre de fichero: mbox.txt
[email protected] 195
Ejercicio ‍2 Crea un programa que cuente la distribución de las horas del día para cada uno de los mensajes. Puedes extraer la hora de la línea “From”, buscando la cadena horaria y luego dividiendo esa cadena en partes mediante el carácter dos-puntos. Una vez que tengas acumulados los contadores para cada hora, imprime en pantalla los contadores, uno por línea, ordenados por hora como se muestra debajo.
Ejecución de ejemplo:
python timeofday.py
Introduzca un nombre de fichero: mbox-short.txt
04 3
06 1
07 1
09 2
10 3
11 6
14 1
15 2
16 4
17 2
18 1
19 1
Ejercicio ‍3 Escribe un programa que lea un archivo e imprima las letras en orden decreciente de frecuencia. El programa debe convertir todas las entradas a minúsculas y contar sólo las letras a-z. El programa no debe contar espacios, dígitos, signos de puntuación, ni nada que sea distinto a las letras a-z. Busca ejemplos de texto en varios idiomas distintos, y observa cómo la frecuencia de las letras es diferente en cada idioma. Compara tus resultados con las tablas de wikipedia.org/wiki/Letter_frequencies.


1
Anécdota: La palabra “tupla” (/tt tuple en inglés), proviene de los nombres dados a las secuencias de números de distintas longitudes: simple, doble, triple, cuádrupe, quíntuple, séxtuple, séptuple, etc.
2
Python no convierte la sintaxis de forma literal. Por ejemplo, si intentas esto con un diccionario, no funcionará exactamente como podrías esperar.
3
Este comportamiento es ligeramente diferente en Python 3.0.

Previous Up Next