Variables y tipos de datos

Ejecutar este documento en forma dinámica: Binder

Nombres de variables

Los nombres de las variables en Python pueden contener caracteres alfanuméricos a-z, A-Z, 0-9 y algunos caracteres especiales como _. Normalmente los nombres de variables comienzan con una letra.

Por convención, los nombres de las variables comienzan con minúsculas, reservándose el comienzo con mayúsculas para los nombres de las clases (veremos algo de orientación a objetos más adelante).

Por otra parte, hay un número de palabras clave, o keywords, que no pueden utilizarse como nombres de variables. Estas keywords son:

and, as, assert, break, class, continue, def, del, elif, else, except, exec, finally, for, from, global, if, import, in, is, lambda, not, or, pass, print, raise, return, try, while, with, yield

Nota: tener cuidado con la keyword lambda, que puede ser un nombre de variable natural en un programa científico, pero al ser una keyword no puede utilizarse de ese modo.

Asignación de variables

El operador de asignación en Python es =. Dado que Python es un lenguaje dinámicamente tipado, no es necesario especificar el tipo de la variable al crear una.

Asignar un valor a una nueva variable crea dicha variable:

In [ ]:
# Asignación de variables
x = 1.0
mi_variable = 13.31

Aunque no lo especifiquemos explícitamente, una variable tiene un tipo asociado con ella. El tipo se deriva del valor que se le asigna:

In [ ]:
type(x)

Si asignamos un nuevo valor a una variable, su tipo puede cambiar:

In [ ]:
x = 1
type(x)

Si queremos utilizar una variable que aún no ha sido definida, obtendremos un error de nombre (NameError):

In [ ]:
print(y)

Tipos fundamentales

En Python, un tipo consiste en dos cosas:

  • Un conjunto de valores.
  • Un conjunto de operaciones que pueden aplicarse a esos valores.

Evaluación de verdadero o falso

Cualquier objeto puede ser evaluado verdadero o falso, para ser utilizado como condición para un bloque de repetición de intrucciones if o while, o como operando de las operaciones booleanas (ver más abajo).

Por defecto, un objeto es considerado verdadero (True) a menos que su clase defina un método __bool__() que devuelva falso (False) o un método __len__() que devuelva cero, cuando se invoquen sobre el objeto. Una lista de la mayoría de los objetos built-in considerados falso es la siguiente:

  • Constantes definidas como falsas: None y False
  • Cero de cualquier tipo numérico: 0, 0.0, 0j, Decimal(0), Fraction(0,1)
  • Secuencias y colecciones vacías: '', (), [], {}, set(), range(0)

Las operaciones y funciones built-in que tienen un resultado booleano siempre devuelven 0 o False para falso y 1 o True, a menos que se establezca de otra forma. Una excepción importante: las operaciones booleanas or y and siempre devuelven uno de sus operandos. Ejemplo:

In [ ]:
a = [1,2,3]
b = False
a or b

Las operaciones booleanas, ordenadas por prioridad ascendente, son:

Operación Resultado Nota
x or y Si x es falso devuelve y, si no x (1)
x and y Si x es falso devuelve x, si no y (2)
not x Si x es falso devuelve True, si no False (3)

Notas:

  1. Solo se evalúa el segundo argumento si el primero es falso
  2. Solo se evalúa el segundo argumento si el primero es verdadero
  3. not tiene menor prioridad que cualquier otro operador booleano, por lo tanto not a == b se interpreta como not (a == b), y a == not b es un error de sintaxis.

Existen ocho operaciones de comparación en Python, y todas tienen la misma prioridad (mayor a las de las operaciones booleanas). Las comparaciones pueden encadenarse abritrariamente, por ejemplo x < y <= z es equivalente a x < y and y <= z, excepto que y se evalúa una sola vez (pero en ambos casos z no se evalúa si x < y es falso). Las operaciones de comparación son:

Operación Significado
< estrictamente menor que
<= menor o igual que
> estrictamente mayor
>= mayor o igual que
== igual
!= no igual
is identidad
is not identidad negada

Tipos numéricos

Python provee tres tipos numéricos distintos: enteros (int), de punto flotante (float) y números complejos (complex). Además, los valores lógicos booleanos (bool) son un subtipo de los enteros. Los enteros tienen precisión ilimitada, mientras que los float son usualmente representados usando el tipo double en C, la información sobre la precisión y representación interna de los números float en cada máquina puede obtenerse en sys.float_info:

In [ ]:
import sys
print(sys.float_info)

Los números son creados mediante literales numéricos o como resultado de funciones built-in y operadores. Literales enteros sin "adornos" producen enteros, los que contienen un punto decimal o un signo de exponente producen floats, y agregando una j o J a un literal numérico obtenemos un número imaginario (complejo con parte real nula).

Python soporta aritmética mixta: cuando un operador aritmético binario tiene operandos de tipos numéricos diferentes, el operando con el tipo más "restringido" se expande al tipo del otro operando. El int es más restringido que el float, que a su vez es más restringido que el complex.

Todos los tipos numéricos (excepto los complejos) suportan las siguientes operaciones, ordenadas por prioridad ascendente:

Operación Resultado Notas
x + y $x + y$
x - y $x - y$
x * y $x y $
x / y $x/y$
x // y división entera de $x$ y $y$ (1)
x % y resto de $x$ y $y$ (2)
-x $-x$
abs(x) $ x $
int(x) $x$ convertido a int (3)(6)
float(x) $x$ convertido a float (4)(6)
complex(x, y) $x + y i$ (6)
c.conjugate() conjugado de $c$
divmod(x, y) el par (x // y), (x % y) (2)
pow(x, y) $x^y$ (5)
x ** y $x^y$ (5)

Notas:

  1. El resultado se redondea siempre hacia $- \infty$.
  2. No es válido para números complejos. Es posible convertir a float usando abs() cuando es apropiado.
  3. Las conversiones desde punto flotante a entero puede redondear o truncar como en C. Para obtener conversiones bien definidas se pueden ver las funciones math.floor() y math.ceil().
  4. float acepta también las cadenas (strings) nan e inf con el prefijo opcional + o - para un "Not a Number" (NaN) y para infinito positivo o negativo.
  5. Python define pow(0, 0) y 0 ** 0 como 1 como es usual en los lenguajes de programación.
  6. Los literales numéricos aceptados incluyen los dígitos 0 a 9 o cualquier Unicode equivalente.

Algunos ejemplos de las operaciones mencionadas:

In [ ]:
x, y = 1, 3
type(y)
In [ ]:
x + y
In [ ]:
x - y
In [ ]:
x * y

La división de dos enteros produce como resultado un valor float:

In [ ]:
z = x / y
print(z)
type(z)

La división entera // devuelve un entero, que siempre se redondea hacia $-\infty$:

In [ ]:
z = x // y
print(z)
type(z)
In [ ]:
(-1) // 2
In [ ]:
1 // (-2)
In [ ]:
(-1) // (-2)

El resto de una división se obtiene con %, y devuelve un entero:

In [ ]:
z = y % x
print(z)
type(z)

Los números complejos tienen una parte real y una imaginaria y cada una de ellas es float. Para extraer cada componente de un número complejo z, se usa z.real y z.imag.

In [ ]:
z = 1.0 - 2.0j
type(z)
In [ ]:
print(z)
In [ ]:
print(z.real, z.imag)

Conversión de tipos (casting):

In [ ]:
x = 1.5
print(x, type(x))
In [ ]:
x = int(x)
print(x, type(x))
In [ ]:
z = complex(x)
print(z, type(z))
In [ ]:
x = float(z)

Las variables complejas no pueden convertirse en float o int. Es necesario hacer uso de z.real o z.imag para obtener la parte del número complejo que queremos.

Tipos compuestos: listas, tuplas, strings, diccionarios y conjuntos

Python provee varios tipos eficientes de contenedores, en donde pueden almacenarse colecciones de objetos.

Las listas son colecciones ordenadas de objetos que pueden ser de tipos diferentes. Por ejemplo:

In [ ]:
colores = ['rojo', 'azul', 'verde', 'blanco', 'negro']
type(colores)

Para acceder a los elementos individuales de la lista se utiliza su índice, teniendo en cuenta que el primer elemento es el 0 (cero) como en C/C++, no el 1 como en FORTRAN o Matlab):

In [ ]:
colores[2]

Para contar desde el final de la lista se utilizan índices negativos:

In [ ]:
print(colores[-1],colores[-3])

Para obtener una sublista de elementos igualmente espaciados se utiliza el rebanado (slicing):

In [ ]:
colores
In [ ]:
colores[2:4]

Nota: el rebanado colores[inicio:fin] contiene los elementos con índices i tales que inicio <= i < fin, es decir, i está en el rango desde inicio hasta fin - 1, por lo tanto colores[inicio:fin] tiene (fin - inicio) elementos.

La sintaxis del rebanado es colores[inicio:fin:paso], siendo todos los parámetros opcionales:

In [ ]:
colores
In [ ]:
colores[3:]
In [ ]:
colores[:3]
In [ ]:
colores[::2]

Las listas son objetos mutables, por lo que pueden ser modificadas:

In [ ]:
colores[1] = 'amarillo'
colores
In [ ]:
colores[2:4] = ['gris', 'cyan']
colores

Los elementos de una lista pueden ser de cualquier tipo (¡incluso otra lista!):

In [ ]:
colores=[1, 3.14, 'hola',['rojo', 'verde']]
colores[1], colores[2], colores[3][1]

Para colecciones de datos numéricos, todos del mismo tipo, es mucho más eficiente utilizar el tipo array provisto por el módulo numpy (que veremos más adelante). Un array de NumPy es un bloque de memoria que contiene ítems de tamaño fijo, por lo que las operaciones sobre los elementos de un array son muy rápidas ya que están distribuidos en forma regular en la memoria y tales operaciones pueden realizarse a través de funciones especializadas de C en vez de bucles de Python.

Python ofrece una extensa lista de funciones para modificar o interrogar listas. Mostraremos algunos ejemplos a continuación (para más detalle ver la documentación en el Tutorial de Python).

Agregar o eliminar elementos:

In [ ]:
colores = ['rojo', 'azul', 'verde', 'blanco', 'negro']
colores.append('cyan')
colores
In [ ]:
colores.pop() # elimina y devuelve el último ítem
In [ ]:
colores
In [ ]:
colores.extend(['rosado', 'púrpura']) # extiende colores "in-place"
colores
In [ ]:
colores = colores[:-2]
colores

Inversión de orden:

In [ ]:
icolores = colores[::-1]
icolores
In [ ]:
icolores2 = list(colores) # icolores2 es una copia de colores
icolores2
In [ ]:
icolores2.reverse() # inversión in-place
print(icolores2)
print(colores)

Concatenación y repetición de listas. Python sobrecarga el operador + y * para operar sobre listas y obtener el resultado intuitivamente esperado:

In [ ]:
icolores + colores
In [ ]:
colores * 2

Python permite ordenar listas generando un nuevo objeto u ordenando la lista in-place:

In [ ]:
sorted(icolores) # Nuevo objeto
In [ ]:
icolores.sort() # in place
icolores

La notación colores.metodo() (por ejemplo colores.append(2) o colores.pop()) es un ejemplo de la sintaxis típica de la programación orientada a objetos (OOP). Por ser una list, el objeto colores posee métodos (funciones) que se invocan usando la notación .. Para este curso solo es importante conocer la notación . y no profundizaremos en la OOP.

En este notebook de Jupyter se puede usar la tecla TAB como auxiliar para descubrir los métodos que posee un determinado objeto:

In [ ]:
colores.

Las tuplas son como las listas, excepto que no se pueden modificar una vez creadas (son inmutables). En Python las tuplas se crean usando la sintaxis (..., ..., ...) o incluso ..., ...:

In [ ]:
point = (10, 20)
print (point, type(point))
In [ ]:
point = 10, 20
print (point, type(point))

Podemos "desempaquetar" una tupla asignándole una lista de variables separadas por comas:

In [ ]:
x, y = point
print('x = ', x)
print('y = ', y)

Si intentamos asignar un nuevo valor a un elemento en una tupla, obtendremos un error:

In [ ]:
point[0] = 5

Strings, o cadenas de caracteres, constituyen el tipo de variable utilizada para almacenar texto:

In [ ]:
s = "Hola mundo"
type(s)
In [ ]:
# Longitud del string: el número de caracteres
len(s)

Existen diferentes sintaxis para delimitar strings (apóstrofos simples, dobles o triples):

In [ ]:
s = 'Hola, ¿qué tal?'
s = "Hi, what's up?"
s = '''Hola,             # triple apóstrofo permite strings de más de una línea
       ¿cómo estás?'''
s = """Hola,
       ¿cómo estás?"""
print(s)

Las strings son colecciones como las listas, por lo que pueden accederse mediante índices y rebanado utilizando la misma sintaxis y reglas:

In [ ]:
a = 'Hola'
a[0]
In [ ]:
a[1]
In [ ]:
a[-1]
In [ ]:
a = '¡Hola mundo!'
a[3:6]
In [ ]:
a[2:10:2]
In [ ]:
a[::3]

Las strings constituyen objetos inmutables por lo que no es posible modificar sus contenidos. Sin embargo es posible crear nuevas strings a partir de una dada:

In [ ]:
a[2] = 'z'
In [ ]:
a.replace('o', 'e', 1)
In [ ]:
a.replace('o', 'e')

Las strings tienen muchos métodos útiles, que pueden explorarse usando TAB o help(str). Uno de los métodos más poderosos es el que sirve para formatear strings con números (ver la documentación):

In [ ]:
'Valor entero: {:d}, valor float: {:.3f}, y exponencial: {:.2E}.'.format(4, 3.1415926, 12345679)

Al igual que con las listas, los operadores + y * realizan la misma función:

In [ ]:
a = 'Hola'
b = 'mundo.'
a + ' ' + b
In [ ]:
5 * (a + ' ') + '!'

Los diccionarios son contenedores no ordenados que mapean eficientemente claves a valores:

In [ ]:
tel = {'manu': 4723, 'marcos': 4981, 'mati': 4112}
tel['ariel'] = 4431
tel
In [ ]:
tel['mati']
In [ ]:
tel.keys()
In [ ]:
tel.values()
In [ ]:
'manu' in tel

Los diccionarios pueden tener cualquier tipo en la clave (aunque no un float) o en el valor:

In [ ]:
d = {1:'a', 'b':3.12, 4:[1,2,'hola']}
d[4]

Los diccionarios y las listas tienen en común las siguientes características:

  • Ambos son mutables.
  • Ambos son dinámicas, pueden crecer o disminuir según se necesite.
  • Ambos pueden anidarse (una lista puede contener otra lista, un diccionario puede contener otro diccionario, un diccionario puede contener una lista y viceversa.

En lo que difieren es que los elementos de la lista se acceden por índice, mientras que los del diccionario por clave.

El último tipo compuesto que veremos son los conjuntos (set), que consisten en contenedores no ordenados con elementos únicos. Pueden crearse con la keyword set con un iterable como argumento, o con llaves y elementos separados por comas:

In [ ]:
s = set(('Mario', 'Alberto', 'Joaquín', 'María', 'Alberto'))
o = {'Ana', 'Marta', 'María'}
print(s)
print(o)

Los conjuntos soportan las operaciones típicas de conjuntos algebraicos:

In [ ]:
s.union(o)
In [ ]:
s.intersection(o)
In [ ]:
len(s) # Indica la cantidad de elementos en el conjunto
In [ ]:
s.difference(('Alberto', 'Mario'))

El operador - está sobrecargado para operar entre conjuntos devolviendo la diferencia:

In [ ]:
print(s.difference(o))
print(s - o)
In [ ]: