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')
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
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.
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
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.
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.
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.
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.
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:
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.
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:
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.
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
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