Apartados


2. Tipos de dato nativos

Nivel de dificultad:2 sobre 5

``La curiosidad es la base de toda la filosofía,
las preguntas alimentan su progreso,
la ignorancia su fin.''
--Michel de Montaigne

2.1 Inmersión

Aparta tu primer programa en Python durante unos minutos, y vamos a hablar sobre tipos de dato. En Python cada valor que exista, tiene un tipo de dato, pero no es necesario declarar el tipo de las variables. ¿Como funciona? Basado en cada asignación a la variable, Python deduce el tipo que es y lo conserva internamente.

Python proporciona muchos tipos de dato nativos. A continuación se muestran los más importantes:

  1. Booleanos: Su valor es True o False.

  2. Números: Pueden ser enteros (1, 2, 3,...), flotantes (1.1, 1.2, 1.3,...)2.1, fracciones (1/2, 1/3, 2/3,...), o incluso números complejos (i = sqrt(- 1)).

  3. Cadenas: Son secuencias de caracteres Unicode, por ejemplo, un documento HTML.

  4. Bytes y arrays de bytes: por ejemplo, un fichero de imágenes JPEG.

  5. Listas: Son secuencias ordenadas de valores.

  6. Tuplas: Son secuencias ordenadas e inmutables de valores.

  7. Conjuntos: Son ``bolsas'' de valores sin ordenar.

  8. Diccionarios: Son ``bolsas'' de sin ordenar de parejas clave-valor. Es posible buscar directamente por clave.

Aparte de estos, hay bastantes más tipos. Todo es un objeto en Python, por lo que existen tipos module, function, class, method, file, e incluso compiled code2.2. Ya has visto alguno de ellos en el capítulo anterior. En el capítulo 7 aprenderás las clases, y en el capítulo 11 los ficheros (también llamados archivos).

Las cadenas y bytes son suficientemente importantes --y complejas-- como para merecer un capítulo aparte. Vamos a ver los otros tipos de dato en primer lugar.

2.2 Booleanos

En la práctica, puedes utilizar casi cualquier expresión en un contexto booleano.

El tipo de datos booleano solamente tiene dos valores posibles: verdadero o falso. Python dispone de dos constantes denominadas True y False, que se pueden utilizar para asignar valores booleanos directamente. Las expresiones también se pueden evaluar a un valor booleano. En algunos lugares (como las sentencias if, Python espera una expresión que se pueda evaluar a un valor booleano. Estos sitios se denominan contextos booleanos. Puedes utilizar casi cualquier expresión en un contexto booleano, Python intentará determinar si el resultado puede ser verdadero o falso. Cada tipo de datos tiene sus propias reglas para identificar qué valores equivalen a verdadero y falso en un contexto booleano (Esto comenzará a tener un sentido más claro para ti cuando veas algunos ejemplos concretos).

Por ejemplo:

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

La variable tamanyo contiene un valor entero, 0 es un entero, y < es un operador numérico. El resultado de la expresión es siempre un valor de tipo booleano. Puedes comprobarlo en la consola interactiva de Python:

»> tamanyo = 1
»> tamanyo < 0
False
»> tamanyo = 0
»> tamanyo < 0
False
»> tamanyo = -1
»> tamanyo < 0
True

Debido a la herencia que se conserva de Python 2, los booleanos se pueden tratar como si fuesen números. True es 1 y False es 0.

»> True + True
2
»> True - False
1
»> True * False
0
»> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero

Aún así, no hagas esto. ¡Olvida incluso que lo he mencionado!2.3

2.3 Números

Los números son maravillosos. Tienes un montón donde elegir. Python proporciona enteros y números de coma flotante, pero no existen declaraciones de tipo para distinguirlos. Python los distingue por la existencia o no del punto decimal2.4.

»> type(1)
<class 'int'>
»> isinstance(1, int)
True
»> 1 + 1
2
»> 1 + 1.0
2.0
»> type(2.0)
<class 'float'>

  1. Línea 1: la función type() permite consultar el tipo de cualquier valor o variable. Como era de esperar 1 es un valor de tipo int.

  2. Línea 3: la función isinstance() permite chequear si un valor o variable es de un tipo determinado.

  3. Línea 5: La suma de dos valores de tipo int da como resultado otro valor de tipo int.

  4. Línea 7: La suma de un valor int con otro de tipo float da como resultado un valor de tipo float. Python transforma el valor entero en un valor de tipo float antes de hacer la suma. El valor que se devuelve es de tipo float.

2.3.1 Convertir enteros en flotantes y viceversa

Como acabas de ver, algunos operadores (como la suma) convierten los números enteros en flotantes si es necesario. También puedes convertirlos tú mismo.

»> float(2)
2.0
»> int(2.0)
2
»> int(2.5)
2
»> int(-2.5)
-2
»> 1.12345678901234567890
1.1234567890123457
»> type(1000000000000000)
<class 'int'>

  1. Línea 1: Utilizando la función float() puedes convertir explícitamente un valor de tipo int en float.

  2. Línea 3: Como era de prever, la conversión inversa se hace utilizando la función int().

  3. Línea 5: La función int() trunca el valor flotante, no lo redondea.

  4. Línea 7: La función int() trunca los valores negativos hacia el 0. Es una verdadera función de truncado, no es una función de suelo2.5.

  5. Línea 9: En Python, la precisión de los números de punto flotante alcanza 15 posiciones decimales.

  6. Línea 11: En Python, la longitud de los números enteros no está limitada. Pueden tener tantos dígitos como se requieran.

Python 2 tenía dos tipos separados int y long. El tipo int estaba limitado por el sistema sys.maxint, siendo diferente según la plataforma, pero usualmente era 232 - 1. Python 3 tiene un único tipo entero que, en su mayor parte, equivale al tipo long de Python 2. Para conocer más detalles consulta PEP 237.

2.3.2 Operaciones numéricas habituales

Puedes hacer muchos tipos de cálculos con números.

»> 11 / 2
5.5
»> 11 // 2
5
»> -11 // 2
-6
»> 11.0 // 2
5.0
»> 11 ** 2
121
»> 11 
1

  1. Línea 1: El operador / efectúa una división en punto flotante. El resultado siempre es de tipo float, incluso aunque ambos operadores (dividendo y divisor) sean int.

  2. Línea 3: El operador // efectúa una división entera algo extraña. Cuando el resultado es positivo, el resultado es int truncado sin decimales (no redondeado).

  3. Línea 5: Cuando el operador // se usa para dividir un número negativo el resultado se redondea hacia abajo al entero más próximo (en este caso el resultado de la división sería -5.5, que redondeado es -5).

  4. Línea 7: El operador ** significa ``elevado a la potencia de''. 112 es 121.

  5. Línea 9: El operador % devuelve el resto de la división entera. 11 dividido entre 2 es 5 con un resto de 1, por lo que el resultado en este caso es 1.

En Python 2, el operador / se usaba para representar a la división entera, aunque mediante una directiva de Python 2, podías hacer que se comportase como una división de punto flotante. En Python 3, el operador / siempre es una división de punto flotante. Para consultar más detalles puedes mirar PEP 238.

2.3.3 Fracciones

Python no está limitado a números enteros y de punto flotante. También puede aplicar toda esa matemática que aprendiste en el instituto y que luego rápidamente olvidaste.

»> import fractions
»> x = fractions.Fraction(1, 3)
»> x
Fraction(1, 3)
»> x * 2
Fraction(2, 3)
»> fractions.Fraction(6, 4)
Fraction(3, 2)
»> fractions.Fraction(0, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(
ZeroDivisionError: Fraction(0, 0)

  1. Línea 1: Para comenzar a utilizar fracciones hay que importar el módulo fractions.

  2. Línea 2: Para definir una fracción crea un objeto Fraction y pásale el numerador y denominador.

  3. Línea 5: Con las fracciones puedes efectuar los cálculos matemáticos habituales. Estas operaciones devuelven un objeto Fraction, 2*(1/2)=(2/3).

  4. Línea 7: El objeto Fraction reduce automáticamente las fracciones: (6/4) = (3/2).

  5. Línea 9: Python tiene el buen sentido de no crear una fracción con el denominador a cero.

2.3.4 Trigonometría

También puedes hacer cálculos trigonométricos en Python.

»> import math
»> math.pi
3.1415926535897931
»> math.sin(math.pi / 2)
1.0
»> math.tan(math.pi / 4)
0.99999999999999989

  1. Línea 2: El módulo math tiene definida una constante que almacena el valor del número π, la razón de la circunferencia de un círculo respecto de su diámetro.

  2. Línea 4: En el módulo math se encuentran todas las funciones trigonométricas básicas, incluidas sin(), cos(), tan() y variantes como asin().

  3. Línea 6: De todos modos ten en cuenta que Python no tiene precisión infinita, tan(π/4) debería devolver 1.0, no 0.99999999999999989.

2.3.5 Números en un contexto booleano

El valor cero es equivalente a falso y los valores distintos de cero son equivalentes a verdadero.

Los números se pueden utilizar en contextos booleanos, como en la sentencia if. El valor cero es equivalente a falso y los valores distintos de cero son equivalentes a verdadero.

»> def es_true(anything):
...   if anything:
...     print("sí, es true")
...   else:
...     print("no, es false")
...
»> es_true(1)
sí, es true
»> es_true(-1)
sí, es true
»> es_true(0)
no, es false
»> es_true(0.1)
sí, es true
»> es_true(0.0)
no, es false
»> import fractions
»> es_true(fractions.Fraction(1, 2))
sí, es true
»> es_true(fractions.Fraction(0, 1))
no, es false

  1. Línea 1: ¿Sabías que puedes definir tus propias funciones en la consola interactiva de Python? Simplemente pulsa INTRO al final de cada línea, y termina pulsando un último INTRO en una línea en planco para finalizar la definición de la función.

  2. Línea 7: En un contexto booleano, como el de la sentencia if, los números enteros distintos de cero se evalúan a True; el número cero se evalúa a False.

  3. Línea 13: Los números en punto flotante distintos de cero son True; 0.0 se evalúa a False. ¡Ten cuidado con este caso! Al más mínimo fallo de redondeo (que no es imposible, como has visto en el apartado anterior) Python se encontraría comprobando el número 0.0000000000001 en lugar del 0 y retornaría True.

  4. Línea 18: Las fracciones también se pueden utilizar en un contexto booleano. Fraction(0, n) se evalúa a False para cualquier valor de n. Todas las otras fracciones se evalúan a True.

2.4 Listas

El tipo de datos List es el más utilizado en Python. Cuando digo ``lista'', puede que pienses en un ``array2.6, cuyo tamaño he declarado anteriormente a su uso, que únicamente puede contener elementos del mismo tipo''. No pienses eso, las listas son mucho más guays.

Una lista de Python es como un array de Perl 5. En Perl 5 las variables que almacenan arrays siempre comienzan con el carácter @. En Python las variables se pueden nombrar como se quiera, ya que Python mantiene el tipo de datos internamente.

Una lista de Python es mucho más que un array de Java (aunque puede utilizarse como si lo fuese si eso es lo que quieres). Una analogía mejor sería pensar en la clase ArrayList de Java, que puede almacenar un número arbitrario de objetos y expandir su tamaño dinámicamente al añadir nuevos elementos.

2.4.1 Crear una lista

Crear una lista es fácil: utiliza unos corchetes para para delimitar una lista de valores separados por coma.

»> lista = ['a', 'b', 'jmgaguilera', 'z', 'ejemplo']
»> lista
['a', 'b', 'jmgaguilera', 'z', 'ejemplo']
»> lista[0]
'a'
»> lista[4]
'ejemplo'
»> lista[-1]
'ejemplo'
»> lista[-3]
'jmgaguilera'

  1. Líneas 1 a 3: Primero definimos una lista de cinco elementos. Observa que mantiene el orden original. No es por casualidad. Una lista es un conjunto ordenado de elementos.

  2. Línea 4: Se puede acceder a los elementos de la lista como en el caso de los arrays de Java, teniendo en cuenta que el primer elemento se numera como cero. El primer elemento de cualquier lista no vacía es lista[0].

  3. Línea 6: El último elemento de esta lista de cinco elementos es lista[4], puesto que los elementos se indexan contando desde cero.

  4. Línea 8: Si se usan índices con valor negativo se accede a los elementos de la lista contando desde el final. El último elemento de una lista siempre se puede indexar utilizando lista[-1].

  5. Línea 10: Si los números negativos en los índices te resultan confusos, puedes pensar de esta forma: lista[-n] == lista[len(lista)-n].
    Por eso: lista[-3] == lista[5 - 3] == lista[2].

2.4.2 Partición de listas

lista[0] es el primer elemento de la lista.

Una vez has definido una lista, puedes obtener cualquier parte de ella como una nueva lista. A esto se le llama particionado2.7 de la lista.

»> lista
['a', 'b', 'jmgaguilera', 'z', 'ejemplo']
»> lista[1:3]
['b', 'jmgaguilera']
»> lista[1:-1]
['b', 'jmgaguilera', 'z']
»> lista[0:3]
['a', 'b', 'jmgaguilera']
»> lista[:3]
['a', 'b', 'jmgaguilera']
»> lista[3:]
['z', 'ejemplo']
»> lista[:]
['a', 'b', 'jmgaguilera', 'z', 'ejemplo']

  1. Línea 3: Puedes obtener una parte de una lista especificando dos índices. El valor de retorno es una nueva lista que contiene los elementos de la lista original, en orden, comenzando en el elemento que estaba en la posición del primer índice (en este caso lista[1]), hasta el elemento anterior al indicado por el segundo índice (en este caso lista[3]).

  2. Línea 5: El particionado de listas también funciona si uno o ambos índices son negativos. Si te sirve de ayuda puedes imaginártelo así: leyendo la lista de izquierda a derecha, el primer índice siempre especifica el primer elemento que quieres obtener y el segundo índice el primer elemento que no quieres obtener. El valor de retorno es una lista con todos los elementos que están entre ambos índices.

  3. Línea 7: Los índices de las listas comienzan a contar en cero, por eso un particionado de lista[0:3] devuelve los primeros tres elementos de la lista, comenzando en lista[0], pero sin incluir lista[3].

  4. Línea 9: Si el primer índice es cero, puedes omitirlo. Python lo deducirá. Por eso lista[:3] es lo mismo que lista[0:3].

  5. Línea 11: De igual forma, si el segundo índice es la longitud de la cadena, puedes omitirlo. Por eso, en este caso, lista[3:] es lo mismo que lista[3:5], al tener esta lista cinco elementos. Existe una elegante simetría aquí. En esta lista de cinco elementos lista[:3] devuelve los 3 primeros elementos y lista[3:] devuelve una lista con los restantes. De hecho, lista[:n] siempre retornará los n primeros elementos y lista[n:] los restantes, sea cual sea el tamaño de la lista.

  6. Línea 13: Si se omiten ambos índices, se obtiene una nueva lista con todos los elementos de la lista original. Es una forma rápida de hacer una copia de una lista.

2.4.3 Añadir elementos a una lista

Existen cuatro maneras de añadir elementos a una lista.

»> lista = ['a']
»> lista = lista + [2.0, 3]
»> lista
['a', 2.0, 3]
»> lista.append(True)
»> lista
['a', 2.0, 3, True]
»> lista.extend(['cuatro', 'omega'])
»> lista
['a', 2.0, 3, True, 'cuatro', 'omega']
»> lista.insert(0, 'omega')
»> lista
['omega', 'a', 2.0, 3, True, 'cuatro', 'omega']

  1. Línea 2: El operador + crea una nueva lista a partir de la concatenación de otras dos. Una lista puede contener cualquier número de elementos, no hay límite de tamaño (salvo el que imponga la memoria disponible). Si embargo, si la memoria es importante, debes tener en cuenta que la concatenación de listas crea una tercera lista en memoria. En este caso, la nueva lista se asigna inmediatamente a la variable lista. Por eso, esta línea de código se efectúa en dos pasos --concatenación y luego asignación-- que puede (temporalmente) consumir mucha memoria cuando las listas son largas.

  2. Línea 3: Una lista puede contener elementos de cualquier tipo, y cada elemento puede ser de un tipo diferente. Aquí tenemos una lista que contiene una cadena de texto, un número en punto flotante y un número entero.

  3. Línea 5: El método append() añade un nuevo elemento, único, al final de la lista (¡Ahora ya tenemos cuatro tipos de dato diferentes en la lista!).

  4. Línea 8: Las listas son clases. ``Crear'' una lista realmente consiste en instanciar una clase. Por eso las listas tienen métodos que sirven para operar con ellas. El método extend() toma un parámetro, una lista, y añade cada uno de sus elementos a la lista original.

  5. Línea 11: El método insert() inserta un único elemento a la lista original. El primer parámetro es el índice del primer elemento de la lista original que se desplazará de su posición para añadir los nuevos. Los elementos no tienen que ser únicos; por ejemplo, ahora hay dos elementos separados en la lista cuyo valor es ``omega'': el primer elemento lista[0] y el último elemento lista[6].

La llamada al método lista.insert(0,valor) es equivalente a la función unshift() de Perl. Añade un elemento al comienzo de la lista, y los restantes elementos se desplazan para hacer sitio.

Vamos a ver más de cerca la diferencia entre append() y extend().

»> lista = ['a', 'b', 'c']
»> lista.extend(['d', 'e', 'f'])
»> lista
['a', 'b', 'c', 'd', 'e', 'f']
»> len(lista)
6
»> lista[-1]
'f'
»> lista.append(['g', 'h', 'i'])
»> lista
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
»> len(lista)
7
»> lista[-1]
['g', 'h', 'i']

  1. Línea 2: El método extend() recibe un único parámetro, que siempre es una lista, y añade cada uno de sus elementos al final de la lista original lista.

  2. Línea 5: Si una lista de tres elementos se extiende con una lista de otros tres elementos, la lista resultante tiene seis elementos.

  3. Línea 9: Por otra parte, el método append() recibe un único parámetro, que puede ser de cualquier tipo. En este caso estamos ejecutando el método pasándole una lista de tres elementos.

  4. Línea 12: Si partes de una lista de seis elementos y añades otra lista a ella, finalizas con una lista de... Siete elementos. ¿Porqué siete? Porque el último elemento (que acabas de añadir) es en sí mismo una lista. Una lista puede contener datos de cualquier tipo, incluidas otras listas. Puede que sea lo que quieras o puede que no. Pero es lo que le has pedido a Python al ejecutar append() con una lista como parámetro.

2.4.4 Búsqueda de valores en una lista

»> lista = ['a', 'b', 'nuevo', 'mpilgrim', 'nuevo']
»> lista.count('nuevo')
2
»> 'nuevo' in lista
True
»> 'c' in lista
False
»> lista.index('mpilgrim')
3
»> lista.index('nuevo')
2
»> lista.index('c')
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list

  1. Línea 2: Como te puedes imaginar, el método count() devuelve el número de veces que aparece un valor específico --el parámetro-- en la lista.

  2. Línea 4: Si lo único que quieres saber es si un valor se encuentra o no en la lista, el operador in es ligeramente más rápido que el método count(). El operador in devuelve True o False, no indica en qué lugar de la lista se encuentra el elemento, ni el número de veces que aparece.

  3. Línea 8: Si necesitas conocer el lugar exacto en el que se encuentra un valor dentro de la lista debes utilizar el método index(). Por defecto, este método buscará en toda la lista, aunque es posible especificar un segundo parámetro para indicar el lugar de comienzo (0 es el primer índice), e incluso, un tercer elemento para indicar el índice en el que parar la búsqueda.

  4. Línea 10: El método index() encuentra la primera ocurrencia del valor en la lista. En este caso el valor ``nuevo'' aparece dos veces, en la posición 2 y en la 4, el método devuelve la posición de la primera ocurrencia: 2.

  5. Línea 12: Puede que no esperases, pero el método index() eleva una excepción ValueError cuando no es capaz de encontrar el elemento en la lista.

¡Espera un momento! ¿Qué significa eso? Pues lo que he dicho: el método index() eleva una excepción si no es capaz de encontrar el valor en la lista. Esto es diferente de la mayoría de los lenguajes de programación que suelen devolver algún índice no válido, como por ejemplo, -1. Aunque al principio te pueda parecer algo desconcertante, creo que con el tiempo llegarás a apreciarlo. Significa que tu programa fallará en la fuente del problema, en lugar de fallar más tarde en algún otro lugar por no haber contemplado la posibilidad de que un elemento no se encontrara en la lista. Recuerda que -1 es un valor de índice válido. Si el método index() devolviera -1... ¡las sesiones de depuración serían bastante complicadas!

2.4.5 Eliminar elementos de una lista

Las listas nunca tienen huecos

Las listas se expanden y contraen de forma automática. Ya has visto como expandirlas. Existen varios modos de eliminar elementos de una lista.

»> lista = ['a', 'b', 'nuevo', 'mpilgrim', 'nuevo']
»> lista[1]
'b'
»> del lista[1]
»> lista
['a', 'nuevo', 'mpilgrim', 'nuevo']
»> lista[1]
'nuevo'

  1. Línea 4: Para eliminar un elemento de una lista puedes utilizar la sentencia del.

  2. Línea 7: Si intentas acceder al elemento en la posición 1 después de borrar el índice 1 no da error. Después de borrar un elemento, todos los elementos que iban detrás de él se desplazan a la izquierda para ``rellenar el vacío'' que dejó el elemento eliminado.

¿Y si no conoces la posición del elemento? No hay problema, en vez de la posición, puedes utilizar el valor del elemento para eliminarlo.

»> lista.remove('nuevo')
»> lista
['a', 'mpilgrim', 'nuevo']
»> lista.remove('nuevo')
»> lista
['a', 'mpilgrim']
»> lista.remove('nuevo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list

  1. Línea 1: Puedes eliminar un elemento de una lista utilizando el método remove(). Este método recibe como parámetro un valor y elimina la primera ocurrencia de ese valor en la lista. Como antes, todos los elementos a la derecha del eliminado, se desplazan a la izquierda para ``rellenar el vacío'', puesto que las listas nunca tienen huecos.

  2. Línea 4: Puedes llamar al método remove() tantas veces como sea necesario. Pero si se intenta eliminar un valor que no se encuentre en la lista, el método elevará una excepción ValueError.

2.4.6 Eliminar elementos de una lista: ronda extra

Otro método de interés que tiene las listas es pop(), que permite eliminar elementos de una lista de un modo especial.

»> lista = ['a', 'b', 'nuevo', 'mpilgrim']
»> lista.pop()
'mpilgrim'
»> lista
['a', 'b', 'nuevo']
»> lista.pop(1)
'b'
»> lista
['a', 'nuevo']
»> lista.pop()
'nuevo'
»> lista.pop()
'a'
»> lista.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from empty list

  1. Línea 2: Cuando se llama sin parámetros, el método pop() elimina el último valor de la lista y devuelve el valor eliminado.

  2. Línea 6: Es posible extraer cualquier elemento de una lista. Para ello hay que pasar el índice deseado al método pop(). Se eliminará el elemento indicado, los siguientes se moverán a la izquierda ``rellenar el vacío'' y se devuelve el valor recién eliminado.

  3. Línea 14: Si llamas al método pop() con una lista vacía se eleva una excepción.

El método pop() sin argumentos se comporta igual que la función pop() de Perl. Elimina el último valor de la lista y lo devuelve. Perl dispone de otra función, shift(), que eliminar el primer elemento y devuelve su valor; en Python es equivalente a lista.pop(0).

2.4.7 Listas en contextos booleanos

Las listas vacías equivalen a falso, todas las demás a verdadero.

Puedes utilizar las listas en contextos booleanos, como en la sentencia if.

»> def es_true(anything):
...   if anything:
...     print("sí, es true")
...   else:
...     print("no, es false")
...
»> es_true([])
no, es false
»> es_true(['a'])
sí, es true
»> es_true([False])
sí, es true

  1. Línea 7: En un contexto booleano una lista vacía vale False.

  2. Línea 9: Cualquier lista con al menos un elemento vale True.

  3. Línea 11: Cualquier lista con al menos un elemento vale True. El valor de los elementos de la lista es irrelevante.

2.5 Tuplas

Una tupla es una lista inmutable. Una tupla no se puede modificar después de haberla creado.

»> tupla = ("a", "b", "mpilgrim", "z", "ejemplo")
»> tupla
('a', 'b', 'mpilgrim', 'z', 'ejemplo')
»> tupla[0]
'a'
»> tupla[-1]
'ejemplo'
»> tupla[1:3]
('b', 'mpilgrim')

  1. Línea 1: Las tuplas se definen de la misma forma que las listas. La única diferencia es que los elementos se cierran entre paréntesis en lugar de corchetes.

  2. Línea 4: Los elementos de una tupla están ordenados como los de una lista. Los índices también comienzan a contar en cero, por lo que el primer elemento de una tupla siempre es tupla[0].

  3. Línea 6: Los índices negativos cuentan desde el final de la tupla como en las listas.

  4. Línea 8: El particionado también funciona como en las listas. Una partición de una tupla es una nueva tupla con los elementos seleccionados.

Lo que diferencia a las tuplas de las listas es que las primeras no se pueden modificar. En términos técnicos se dice que son inmutables. En términos prácticos esto significa que no tienen métodos que te permitan modificarlas. Las listas tienen métodos como append(), extend(), insert(), remove() y pop(). Las tuplas no tienen ninguno de estos métodos. Puedes particionar una tupla porque en realidad se crea una nueva tupla, y puedes consultar si contienen un valor determinado, y... Eso es todo.

# continuación del ejemplo anterior
»> tupla
('a', 'b', 'mpilgrim', 'z', 'ejemplo')
»> tupla.append("nuevo")
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tupla' object has no attribute 'append'
»> tupla.remove("z")
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tupla' object has no attribute 'remove'
»> tupla.index("ejemplo")
4
»> "z" in tupla
True

  1. Línea 4: No puedes añadir elementos a una tupla. No existen los métodos append() o extend().

  2. Línea 8: No puedes eliminar elementos de una tupla. No existen los métodos remove() o pop().

  3. Línea 12: Sí puedes buscar elementos en una tupla puesto que consultar no cambia la tupla.

  4. Línea 14: También puedes utilizar el operador in para chequear si existe un elemento en la tupla.

¿Para qué valen las tuplas?

Las tuplas se pueden convertir en listas y viceversa. La función interna tuple() puede recibir como parámetro una lista y devuelve una tupla con los mismos elementos que tenga la lista, y la función list() toma como parámetro una tupla y retorna una lista. En la práctica la función tuple() ``congela'' una lista, y la función list() ``descongela'' una tupla.

2.5.1 Tuplas en un contexto booleano

Las tuplas también se pueden utilizar en un contexto booleano:

»> def es_true(anything):
...   if anything:
...     print("sí, es true")
...   else:
...     print("no, es false")
...
»> es_true(())
no, es false
»> es_true(('a', 'b'))
sí, es true
»> es_true((False,))
sí, es true
»> type((False))
<class 'bool'>
»> type((False,))
<class 'tuple'>

  1. Línea 7: Una tupla vacía siempre vale false en un contexto booleano.

  2. Línea 9: Una tupla con al menos un valor vale true.

  3. Línea 11: Una tupla con al menos un valor vale true. El valor de los elementos es irrelevante. Pero ¿qué hace esa coma ahí?

  4. Línea 13: Para crear una tupla con un único elemento, necesitas poner una coma después del valor. Sin la coma Python asume que lo que estás haciendo es poner un par de paréntesis a una expresión, por lo que no se crea una tupla.

2.5.2 Asignar varios valores a la vez

A continuación se observa una forma muy interesante de programar múltiples asignaciones en Python. Para ello utilizamos las tuplas:

»> v = ('a', 2, True)
»> (x, y, z) = v
»> x
'a'
»> y
2
»> z
True

  1. Línea 2: v es una tupla con tres elementos y (x, y, z) es una tupla con tres variables. Al asignar una tupla a la otra, lo que sucede es que cada una de las variables recoge el valor del elemento de la otra tupla que corresponde con su posición.

Esto tiene toda clase de usos. Supón que quieres asignar nombres a un rango de valores, puedes combinar la función range() con la asignación múltiple para hacerlo de una forma rápida:

»> (LUNES, MARTES, MIERCOLES, JUEVES,
... VIERNES, SABADO, DOMINGO) = range(7)
»> LUNES
0
»> MARTES
1
»> DOMINGO
6

  1. Línea 1: La función interna range() genera una secuencia de números enteros2.8. Las variables que vas a definir son LUNES, MARTES, etc)2.9. El módulo calendar define unas constantes enteras para cada día de la semana).

  2. Línea 3: Ahora cada variable tiene un valor: LUNES vale 0, MARTES vale 1, etc.

También puedes utilizar la asignación múltiple para construir funciones que devuelvan varios valores a la vez. Simplemente devolviendo una tupla con los valores. Desde el código que llama a la función se puede tratar el valor de retorno como una tupla o se puede asignar los valores individuales a unas variables. Muchas librerías estándares de Python hacen esto, incluido el módulo os, que utilizaremos en el siguiente capítulo.

2.6 Conjuntos

Un conjunto es una ``bolsa'' sin ordenar de valores únicos. Un conjunto puede contener simultáneamente valores de cualquier tipo de datos. Con dos conjuntos se pueden efectuar las típicas operaciones de unión, intersección y diferencia de conjuntos.

2.6.1 Creación de conjuntos

Comencemos por el principio, crear un conjunto es fácil.

»> un_conjunto = 1
»> un_conjunto
1
»> type(un_conjunto)
<class 'set'>
»> un_conjunto = 1, 2
»> un_conjunto
1, 2

  1. Línea 1: Para crear un conjunto con un valor basta con poner el valor entre llaves ().

  2. Línea 4: Los conjuntos son clases, pero no te preocupes por ahora de esto.

  3. Línea 6: Para crear un conjunto con varios elementos basta con separarlos con comas y encerrarlos entre llaves.

También es posible crear un conjunto a partir de una lista:

»> una_lista = ['a', 'b', 'mpilgrim', True, False, 42]
»> un_conjunto = set(una_lista)
»> un_conjunto
'a', False, 'b', True, 'mpilgrim', 42
»> una_lista 
['a', 'b', 'mpilgrim', True, False, 42]

  1. Línea 2: Para crear un conjunto de una lista utiliza la función set()2.10.

  2. Línea 3: Como comenté anteriormente, un conjunto puede contener valores de cualquier tipo y está desordenado. En este ejemplo, el conjunto no recuerda el orden en el que estaba la lista que sirvió para crearlo. Si añadieras algún elemento nuevo no recordaría el orden en el que lo añadiste.

  3. Línea 5: La lista original no se ha modificado.

¿Tienes un conjunto vacío? Sin problemas. Puedes crearlo y más tarde añadir elementos.

»> un_conjunto = set()
»> un_conjunto
set()
»> type(un_conjunto)
<class 'set'>
»> len(un_conjunto)
0
»> no_seguro = 
»> type(no_seguro)
<class 'dict'>

  1. Línea 1: Para crear un conjunto vacío debes utilizar la función set() sin parámetros.

  2. Línea 2: La representación impresa de un conjunto vacío parece algo extraña. ¿Tal vez estabas esperando {}? Esa expresión se utiliza para representar un diccionario vacío, no un conjunto vacío. Aprenderás a usar los diccionarios más adelante en este capítulo.

  3. Línea 4: A pesar de la extraña representación impresa se trata de un conjunto.

  4. Línea 6: ...y este conjunto no tiene elementos.

  5. Línea 8: Debido a razones históricas procedentes de Python 2. No puedes utilizar las llaves para crear un conjunto vacío, puesto que lo que se crea es un diccionario vacío, no un conjunto vacío.

2.6.2 Modificación de conjuntos

Hay dos maneras de añadir valores a un conjunto: el método add() y el método update().

»> un_conjunto = 1, 2
»> un_conjunto.add(4)
»> un_conjunto
1, 2, 4
»> len(un_conjunto)
3
»> un_conjunto.add(1)
»> un_conjunto
1, 2, 4
»> len(un_conjunto)
3

  1. Línea 2: El método add() recibe un parámetro, que puede ser de cualquier tipo, cuyo resultado es añadir el parámetro al conjunto.

  2. Línea 5: Este conjunto tiene ahora cuatro elementos.

  3. Línea 7: Los conjuntos son ``bolsas'' de valores únicos. Por eso, si intentas añadir un valor que ya exista en el conjunto no hará nada. Tampoco elevará un error. Simplemente no se hace nada.

  4. Línea 10: Por eso, el conjunto aún tiene tres elementos.

»> un_conjunto = 1, 2, 3
»> un_conjunto
1, 2, 3
»> un_conjunto.update(2, 4, 6)
»> un_conjunto
1, 2, 3, 4, 6
»> un_conjunto.update(3, 6, 9, 1, 2, 3, 5, 8, 13)
»> un_conjunto
1, 2, 3, 4, 5, 6, 8, 9, 13
»> un_conjunto.update([10, 20, 30])
»> un_conjunto
1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30

  1. Línea 4: El método update() toma un parámetro, un conjunto, y añade todos sus elementos al conjunto original. Funciona como si llamaras al método add() con cada uno de los elementos del conjunto que pasas como parámetro.

  2. Línea 5: Los elementos duplicados se ignoran puesto que los conjuntos no pueden contener duplicados.

  3. Línea 7: Puedes llamar al método update() con cualquier número de parámetros. Cuando lo llamas con dos conjuntos, el método añade todos los elementos de cada conjunto al conjunto original (sin incluir duplicados).

  4. Línea 10: El método update() puede recibir como parámetro elementos de diferentes tipos de dato, incluidas las listas. Cuando se llama pasándole una lista, el método update() añade todos los elementos de la lista al conjunto original.

2.6.3 Eliminar elementos de un conjunto

Existen tres formas de eliminar elementos individuales de un conjunto: Las dos primeras discard() y remove(), se diferencian de forma sutil:

»> un_conjunto = 1, 3, 6, 10, 15, 21, 28, 36, 45
»> un_conjunto
1, 3, 36, 6, 10, 45, 15, 21, 28
»> un_conjunto.discard(10)
»> un_conjunto
1, 3, 36, 6, 45, 15, 21, 28
»> un_conjunto.discard(10)
»> un_conjunto
1, 3, 36, 6, 45, 15, 21, 28
»> un_conjunto.remove(21)
»> un_conjunto
1, 3, 36, 6, 45, 15, 28
»> un_conjunto.remove(21)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 21

  1. Línea 4: El método discard() toma un único parámetro y elimina el elemento del conjunto.

  2. Línea 7: Si llamas al método discard() con un valor que no exista en el conjunto no se produce ningún error. Simplemente no se hace nada.

  3. Línea 10: El método remove() también recibe un único parámetro y también elimina el elemento del conjunto.

  4. Línea 13: Aquí esta la diferencia: si el valor no existe en el conjunto, el método remove() eleva la excepción KeyError.

Como pasa con las listas, los conjuntos también tienen el método pop():

»> un_conjunto = 1, 3, 6, 10, 15, 21, 28, 36, 45
»> un_conjunto.pop()
1
»> un_conjunto.pop()
3
»> un_conjunto.pop()
36
»> un_conjunto
6, 10, 45, 15, 21, 28
»> un_conjunto.clear()
»> un_conjunto
set()
»> un_conjunto.pop()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'

  1. Línea 2: El método pop() elimina un único valor del conjunto y retorna el valor. Sin embargo, como los conjuntos no están ordenados, no hay un ``último'' elemento, por lo que no hay forma de controlar qué elemento es el que se extrae. Es aleatorio.

  2. Línea 10: El método clear() elimina todos los valores del conjunto dejándolo vacío. Es equivalente a un_conjunto = set(), que crearía un nuevo conjunto vacío y lo asignaría a la variable, eliminando el conjunto anterior.

  3. Línea 13: Si se intenta extraer un valor de un conjunto vacío se eleva la excepción KeyError.

2.6.4 Operaciones típicas de conjuntos

Los conjuntos de Python permiten las operaciones habituales de este tipo de datos:

»> un_conjunto = 2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195
»> 30 in un_conjunto
True
»> 31 in un_conjunto
False
»> otro_conjunto = 1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21
»> un_conjunto.union(otro_conjunto)
1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127
»> un_conjunto.intersection(otro_conjunto)
9, 2, 12, 5, 21
»> un_conjunto.difference(otro_conjunto)
195, 4, 76, 51, 30, 127
»> un_conjunto.symmetric_difference(otro_conjunto)
1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51

  1. Línea 2: Para comprobar si un valor está contenido en un conjunto se puede utilizar el operador in. Funciona igual que en las listas.

  2. Línea 7: El método union() retorna un nuevo conjunto que contiene todos los elementos que están en alguno de los conjuntos originales.

  3. Línea 9: El método intersection() retorna un nuevo conjunto con los elementos que están en ambos conjuntos originales.

  4. Línea 11: El método difference() retorna un nuevo conjunto que contiene los elementos que están en un_conjunto pero no en otro_conjunto.

  5. Línea 13: El método symmetric_difference() retorna un nuevo conjunto que contiene todos los elementos que están únicamente en uno de los conjuntos originales.

Tres de estos métodos son simétricos:

# continuación del ejemplo anterior
»> otro_conjunto.symmetric_difference(un_conjunto)
3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127
»> otro_conjunto.symmetric_difference(un_conjunto) ==  ... un_conjunto.symmetric_difference(otro_conjunto)
True
»> otro_conjunto.union(un_conjunto) ==  ... un_conjunto.union(otro_conjunto)
True
»> otro_conjunto.intersection(un_conjunto) ==  ... un_conjunto.intersection(otro_conjunto)
True
»> otro_conjunto.difference(un_conjunto) ==  ... un_conjunto.difference(otro_conjunto)
False

  1. Línea 2: Aunque el resultado de la diferencia simétrica de un_conjunto y otro_conjunto parezca diferente de la diferencia simétrica de otro_conjunto y un_conjunto, recuerda que los conjuntos están desordenados. Dos conjuntos con los mismos valores se consideran iguales.

  2. Línea 4: Y eso es lo que sucede aquí. No te despistes por la representación impresa de los conjuntos. Como contienen los mismos valores, son iguales.

  3. Línea 7: La unión de dos conjuntos también es simétrica.

  4. Línea 10: La intersección de dos conjuntos también es simétrica.

  5. Línea 13: La diferencia de dos conjuntos no es simétrica, lo que tiene sentido, es análogo a la resta de dos números; importa el orden de los operandos.

Finalmente veamos algunas consultas que se pueden hacer a los conjuntos:

»> un_conjunto = 1, 2, 3
»> otro_conjunto = 1, 2, 3, 4
»> un_conjunto.issubset(otro_conjunto)
True
»> otro_conjunto.issuperset(un_conjunto)
True
»> un_conjunto.add(5)
»> un_conjunto.issubset(otro_conjunto)
False
»> otro_conjunto.issuperset(un_conjunto)
False

  1. Línea 3: un_conjunto es un subconjunto de otro_conjunto --Todos los miembros de un_conjunto forman parte de otro_conjunto.

  2. Línea 5: Pregunta lo mismo pero al revés. otro_conjunto es un superconjunto de un_conjunto --Todos los miembros de un_conjunto forman parte de otro_conjunto.

  3. Línea 7: Tan pronto como añadas un valor a un_conjunto que no se encuentre en otro_conjunto ambas consultas devuelven False.

2.6.5 Los conjuntos en contextos booleanos

Puedes utilizar conjuntos en contextos booleanos, como en una sentencia if.

»> def es_true(algo):
...   if algo:
...     print("sí, es true")
...   else:
...     print("no, es false")
...
»> es_true(set())
no, es false
»> es_true('a')
sí, es true
»> es_true(False)
sí, es true

  1. Línea 7: En un contexto booleano los conjuntos vacíos valen False.

  2. Línea 9: Cualquier conjunto con al menos un elemento vale True.

  3. Línea 11: Cualquier conjunto con al menos un elemento vale True. El valor de los elementos es irrelevante.

2.7 Diccionarios

Un diccionario es un conjunto desordenado de parejas clave-valor. Cuando añades una clave a un diccionario, tienes que añadir también un valor para esa clave2.11. Los diccionarios de Python están optimizados para recuperar fácilmente el valor cuando conoces la clave, no al revés2.12.

Un diccionario de Python, es como un hash de Perl 5. En Perl 5 las variables que almacenan ``hashes'' siempre comienzan por el carácter %. En Python, las variables pueden tener el nombre que se quiera porque el tipo de datos se mantiene internamente.

2.7.1 Creación de diccionarios

Crear diccionarios es sencillo. La sintaxis es similar a la de los conjuntos, pero en lugar de valores, tienes que poner parejas clave-valor. Una vez has creado el diccionario, puedes buscar los valores mediante el uso de su clave.

»> un_dic = 'servidor': 'db.diveintopython3.org',
              'basedatos': 'mysql'
»> un_dic
'servidor': 'db.diveintopython3.org', 'basedatos': 'mysql'
»> un_dic['servidor']
'db.diveintopython3.org'
»> un_dic['basedatos']
'mysql'
»> un_dic['db.diveintopython3.org']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'

  1. Línea 1: En el ejemplo creamos primero un diccionario con dos elementos y lo asignamos a la variable un_dic. Cada elemento es una pareja clave-valor. El conjunto completo de elementos se encierra entre llaves.

  2. Línea 5: ``servidor'' es una clave, y su valor asociado se obtiene mediante la referencia un_dic[``servidor''] cuyo valor es ``db.diveintopython3.org''.

  3. Línea 7: ``basedatos'' es una clave y su valor asociado se obtiene mediante la referencia un_dic[``basedatos''] cuyo valor es ``mysql''.

  4. Línea 9: Puedes recuperar los valores mediante la clave, pero no puedes recuperar las claves mediante el uso de su valor. Por eso un_dic[``servidor''] vale ``db.diveintopython3.org'' pero un_dic[``db.diveintopython3.org''] eleva una excepción de tipo KeyError al no ser una clave del diccionario.

2.7.2 Modificación de un diccionario

»> un_dic
'servidor': 'db.diveintopython3.org', 'basedatos': 'mysql'
»> un_dic['basedatos'] = 'blog'
»> un_dic
'servidor': 'db.diveintopython3.org', 'basedatos': 'blog'
»> un_dic['usuario'] = 'mark'
»> un_dic
'servidor': 'db.diveintopython3.org', 'usuario': 'mark', 
 'basedatos': 'blog'
»> un_dic['usuario'] = 'dora'
»> un_dic
'servidor': 'db.diveintopython3.org', 'usuario': 'dora', 
 'basedatos': 'blog'
»> un_dic['Usuario'] = 'mark'
»> un_dic
'Usuario': 'mark', 'servidor': 'db.diveintopython3.org', 
 'usuario': 'dora', 'basedatos': 'blog'

  1. Línea 3: No puedes tener claves duplicadas en un diccionario. Al asignar un valor a una clave existente el valor anterior se pierde.

  2. Línea 6: Puedes añadir nuevas parejas clave-valor en cualquier momento. La sintaxis es idéntica a la que se utiliza para modificar valores.

  3. Línea 8: El elemento nuevo del diccionario (clave ``usuario'', valor ``mark'') aparece en la mitad. Esto es una mera coincidencia, los elementos de un diccionario no están ordenados.

  4. Línea 10: Al asignar un valor a una clave existente, simplemente se sustituye el valor anterior por el nuevo.

  5. Línea 14: Esta sentencia ¿cambia el valor de la clave ``usuario'' para volverle asignar ``mark''? ¡No! Si lo observas atentamente verás que la ``U'' está en mayúsculas. Las claves de los diccionarios distinguen las mayúsculas y minúsculas, por eso esta sentencia crea una nueva pareja clave-valor, no sobreescribe la anterior. Puede parecerte casi lo mismo, pero en lo que a Python respecta, es totalmente diferente.

2.7.3 Diccionarios con valores mixtos

Los diccionarios no se usan únicamente con cadenas de texto. Los valores de un diccionario pueden ser de cualquier tipo, incluidos enteros, booleanos, cualquier objeto o incluso otros diccionarios. Y en un mismo diccionario, no es necesario que todos los valores sean del mismo tipo, puedes mezclarlos según lo necesites. Los tipos de datos que pueden ser claves de un diccionario están más limitados, pero pueden ser cadenas de texto, enteros, y algunos tipos más. También es factible mezclar diferentes tipos de clave en un mismo diccionario.

De hecho, ya hemos visto un diccionario con valores diferentes a cadenas de texto.

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

Vamos a descomponerlo en la consola interactiva de Python.

»> SUFIJOS = 1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...     1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
»> len(SUFIJOS)
2
»> 1000 in SUFIJOS
True
»> SUFIJOS[1000]
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
»> SUFIJOS[1024]
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
»> SUFIJOS[1000][3]
'TB'

  1. Línea 3: Como sucede con las listas y conjuntos, la función len() devuelve el número de claves que tiene un diccionario.

  2. Línea 5: También como pasa con las listas y conjuntos puedes utilizar el operador in para comprobar si una clave determinada está en el diccionario.

  3. Línea 7: 1000 es una clave del diccionario SUFIJOS; su valor es una lista de ocho elementos (ocho cadenas de texto, por ser más precisos).

  4. Línea 9: De igual manera, 1024 es una clave del diccionario SUFIJOS; su valor también es una lista de ocho elementos.

  5. Línea 11: Puesto que SUFIJOS[1000] es una lista, puedes utilizar los corchetes para acceder a los elementos individuales. Recuerda que los índices en Python comienzan a contar en cero.

2.7.4 Diccionarios en un contexto booleano

También puedes utilizar un diccionario en un contexto booleano, como en la sentencia if.

Todo diccionario vacío equivale a False y todos los demás equivalen a True.

»> def es_true(algo):
...   if algo:
...     print("sí, es true")
...   else:
...     print("no, es false")
...
»> es_true()
no, es false
»> es_true('a' : 1)
sí, es true

  1. Línea 7: En un contexto booleano un diccionario vacío equivale a False.

  2. Línea 9: Cualquier diccionario con, al menos, una pareja clave-valor equivale a True.

2.8 None

None es una constante especial de Python. Representa al valor nulo. None no es lo mismo que False. None tampoco es 0. None tampoco es la cadena vacía. Cualquier comparación de None con otra cosa diferente de él mismo se evalúa al valor False.

None es el único valor nulo. Tiene su propio tipo de dato (NoneType). Puedes asignar None a cualquier variable, pero no puedes crear nuevos objetos del tipo NoneType. Todas las variables cuyo valor es None son iguales entre sí.

»> type(None)
<class 'NoneType'>
»> None == False
False
»> None == 0
False
»> None == ''
False
»> None == None
True
»> x = None
»> x == None
True
»> y = None
»> x == y
True

2.8.1 None en un contexto booleano

En un contexto booleano None vale False y not None vale True.


»> def es_true(algo): ... if algo: ... print("sí, es true") ... else: ... print("no, es false") ... »> es_true(None) no, es false »> es_true(not None) sí, es true

2.9 Lecturas complementarias

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