Introducción a Python

Python es un lenguaje de programación interpretado, como lo son Matlab o IDL, pero a diferencia de ellos, es de uso general y no específicamente orientado a ciencia o ingeniería. A diferencia de lenguajes interpretados como C/C++ o Fortran, que hay que compilar para crear un programa ejecutable, Python se ejecuta (interpreta) línea a línea, lo que lo hace ideal para en análisis interactivo de datos.

Además de incluir una extensa librería propia, existen módulos de terceros para prácticamente cualquier cosa. Python incluye una terminal estándar sobre la que se pueden ejecutar comandos, pero existen otras alternativas, como ipython, mucho más completas. Además Python se puede ejecutar como un programa ejecutable desde la línea comandos.

Nota

Python 2 vs Python 3

Desde 2008 coexisten dos ramas de Python, Python 2 y Python 3. Aunque son muy similares, existen algunas diferencias entre ellas que hacen que no son completamente compatibles. La versión 2.7 será la última de Python 2, que llegará al final de de vida (EOL) en enero de 2020, no habiendo más actualizaciones ni corrección de errores. Por este motivo debe usarse Python 3.

Si embargo, aun hay algunos programas y sistemas que usan Python 2 y es aún la versión instalada por defecto en Linux y MacOS, por lo que conviene conocer las diferencias entre ambos.

Empezaremos con la terminal estándar de Python escribiendo python en la terminal:

japp@vega:~$ python3
Python 3.6.8 (default, Oct  7 2019, 12:59:55)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Esto es Python 3")
Esto es Python 3

Los tres símbolos de mayor (>>>) es el prompt que espera los comandos. Con la consola podemos hacer las operaciones matemáticas básicas directamente:

>>> 23*119
2737
>>> 34 - 57 * (13 + 3)**2
>>> -14558

Sin embargo, ahora ya no estamos en la terminal de comandos del sistema, por lo no podemos interactuar con él directamente y los comandos del SO no funcionan:

>>> pwd
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'pwd' is not defined

Desde luego existen formas de llamar a commandos y programas del sistema como ya veremos, pero esto nos muestra algunas limitaciones de la consolar estándar. Por este y otros motivos nos conviene usar una consola de comandos avanzada como es IPython. Si tenemos una instalación estándar de Python e IPython, podemos usar lanzar IPython desde la línea de comandos con ipython; si usamos Anaconda podemos usar la Jupyter Qtconsole, que es una versión gráfica de IPython.

japp@vega:~$ ipython3
Python 3.6.8 (default, Oct  7 2019, 12:59:55)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.7.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: ls
Documentos/  imagen.png  texto.txt

In [2]: pwd
/home/japp

Como vemos, al menos los comandos básicos del sistema funcionan directamente. Lo más destacado de IPython son los comandos mágicos , que son funcionalidades adicionales muy útiles; todas empiezan con «%» y podemos ver una referencia rápida de estos con %quickref y una referencia general con %magic. Pronto usarmos más comandos mágicos, por ahora empezaremos con %logstart para guardar un registro de nuestra sesión de trabajo.

In [4]: %logstart -o introduccion_a_python-20191125.py
Activating auto-logging. Current session state plus future input saved.
Filename       : introduccion_a_python-20191125.py
Mode           : backup
Output logging : True
Raw input log  : False
Timestamping   : False
State          : active

In [5]:

donde el parámetro opcional -o guarda también la salida (respuesta) de Python en el fichero introduccion_a_python-16May2017.py. De esta manera tendremos en un fichero todo lo que hicimos durante la sesión. Con los comandos %logoff y %logon podemos, respectivamente, detener temporalmente y continuar el registro de sesión que hayamos iniciado previamente con %logstart. El nombre del fichero es libre y también puede tener cualquier extensión. En este ejemplo usamos .py porque en el fondo contiene código Python, pero en realidad no es exactamente un ejecutable porque contiene todos los errores que hayamos podido cometer en el proceso.

Nota

Comandos mágicos de IPython

Algunos comandos mágicos más de IPython

%history: Muestra los últimos comandos usados. %who y %whos: Lista simple y detallada de las variables declaradas en la sesión. %edit <filename>: Abre un fichero con editor y después lo ejecuta. %paste: Pega código en la terminal sin problemas de espacios y sangrado. %timeit: calcula el tiempo ejecución de una línea de código.

Como casi todos los lenguajes de programación, Python distingue entre enteros (int), coma flotante (float) y cadenas de texto (str) aunque posee otros muchos tipos de datos. Como es habitual también en muchos lenguages, en Python 2 las operaciones entre enteros siempre devuelven un entero, por ejemplo:

# Usando Python 2
113/27
4

113/27.0
4.1851851851851851

Esto ocurre porque el tipo de dato de salida es siempre el de mayor precisión entre los valores de entrada; si solo se usan enteros, el tipo de dato devuelto será un entero aunque su valor numérico no lo sea. Esta situación cambia en Python 3, que cambia a float en divisiones exactas, pero hay que tenerlo muy en cuenta con Python 2.

# Usando Python 3
113/27
4.185185185185185

# Asi se reproduce el funcionamiento de Python 2
113//27
4

La variables pueden llamarse usando cualquier cadena alfanumérica, pero sin caracteres especiales como espacios, o &, $, *, etc., siempre que no empiece con un número. Además distingue entre mayúsculas y minúsculas y no es necesario declararlas previamente.

In [10]: frase = "Esta es una linea de texto"
In [11]: num = 22
In [12]: num*2
Out[12]: 44
In [13]: frase*2
Esta es una linea de textoEsta es una linea de texto

Aquí, frase es una variable tipo str (string) mientras que num es otra variable numérica entera int. Nótese que mientras se multiplicó num por 2 como era de esperar, la cadena de texto frase fue duplicada al multiplicarla por 2. En este caso hemos operado con dos tipos de datos distintos, un string y un int, algo que muchos lenguajes de programación produce un error por no tener sentido, sin embargo, Python interpreta el producto de un string y un int como la unión o concatenación de la misma cadena de texto varias veces, y es por eso que vemos frase duplicada. Los tipos de datos numéricos de Python son int, float y complex. En Python 2 existe el entero long, pero en Python 3 solo hay enteros tipos int que básicamente se comporta como long de Python 2.

A lo largo del trabajo podemos acabar definiendo muchas variables de distinto tipo. Con el comando type() podemos saber el tipo de dato que se asigna a una variable si en cualquier momento no recordamos como la definimos:

In [15]: type(frase)
Out[15]: str
In [16]: type(num)
Out[16]: int

In [17]: complejo = 1.2 + 5.0j   # tambien: complex(1.2, 5.0)
In [18]: type(complejo)
Out[18]: complex

Algunos tipos de datos pueden convertirse de unos a otros, empleando str() para convertir a cadena de texto, int() a entero y float() a coma flotante:

In [20]: float(3)
3.0

In [21]: int(3.1416)  # Parte entera del float
3

In [22]: str(34)
'34'

Para el caso de los float, se pueden redondear con round(), que redondea al entero más próximo. Las funciones ceil() y floor() del módulo math redondean hacia arriba y hacia abajo respectivamente, como veremos mas adelante:

In [30]: print(round(4.4)) , (round(4.5))
Out[30]: 4.0 5.0

Además de los operadores aritméticos conocidos ** (exponenciación, ^ es XOR en operaciones bit a bit), *, /, % (módulo), +\ y -, tenemos los operadores lógicos:

Operación

Símbolo

Igual a (comparación)

==

Distinto de (comparación)

!= o <>

Mayor que, Menor que

>, <

Mayor o igual, Menor o igual

>=, =<

y, o

and, or

cierto, falso

True, False

Como resultado de una operación lógica, obtenemos como respuesta un elemento booleano True o False, según se verifique o no la operación. Estos elementos lógicos los podemos usar a su vez para otras operaciones. Veamos algunos ejemplos:

In  [40]: resultado = 8 > 5
In  [41]: print(resultado)
True

In  [42]: resultado = (4 > 8) or (3 > 2)
In  [43]: print(resultado)
True

In  [44]: resultado = True and False
In  [45]: print(resultado)
False

In  [46]: resultado = (4 > 8) and (3 > 2)
In  [47]: print(resultado)
False

Usando los operadores lógicos, podemos consultar si una variable es de un tipo en concreto:

In  [50]: numero = 10.0
In  [51]: type(numero) == int
Out [51]: False
In  [52]: type(numero) == float
Out [52]: True

En este caso int y float no son cadenas de texto, sino un indicador del tipo de dato. Esto se puede usar para cualquier tipo de dato más complejos, no únicamente números o cadenas, los cuales veremos más adelante.

Cadenas de texto

Las cadenas de texto, como hemos visto, no son mas que texto formado por letras y números de cualquier longitud y son fácilmente manipulables. Para poder hacerlo, cada carácter de una cadena de texto tiene asociado un índice que indica su posición en la cadena, siendo 0 el de la izquierda del todo (primero), 1 el siguiente hacia la derecha y así sucesivamente hasta el último a la derecha. Aquí hay algunos ejemplos:

In [52]: # Variable "frase" que contiene una cadena de texto
In [53]: frase = "hombros de gigantes"
In [54]: print(frase[0])       # Primera letra de la cadena
h

In [55]: print(frase[11])     # Decimosegunda letra, con índice 11
g

In [57]: print(frase[11:18])  # Seccion de la cadena
gigante

In [58]: print(frase[11:])    # Desde el indice 11 hasta el final
gigantes

In [58]: print(frase[:10])    # Desde el principio al caracter de
hombros de                    # indice 10, sin incluirlo
indexado de cadenas

Indexado de cadenas de texto usando índices positivos empezando desde la izquierda con 0 y negativos, empezando desde la derecha con -1.

El comando len() nos da el número de caracteres (longitud) de la cadena de texto, incluyendo espacios en blanco y caracteres especiales:

In  [60]: len(frase)                 # 75 es el número de caracteres de la cadena
Out [60]: 19
In  [61]: print(frase[len(frase)-1]) # El último carácter, contando desde la izquierda
s
In  [62]: print( frase[-1] == frase[len(frase)-1] ) # Compruebo si son iguales
True

Como vemos arriba, también se pueden referir con índices contando desde la derecha, usando índices negativos, siendo -1 el primero por la derecha:

In  [63]: print(frase[-1])    # El último carácter, contando desde la derecha
s

Si hacemos dir() de la variable frase, veremos los métodos (funciones) que se pueden aplicar frase por ser un string. Los métodos son simplemente funciones específicas para una tipo (objeto) de dato:

In  [65]: type(frase)
Out [65]: str

In  [66]: dir(frase)
Out [66]: ['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__doc__',
 .
 .
  'upper',
 'zfill']

los primeros, que empiezan y terminan con «__» son variables internas del módulo, un tipo de métodos y propiedades especiales; el resto son los métodos normales. Una manera más práctica de verlos, gracias a IPython es escribir un punto después de frase (u otro tipo de variable) y luego presionar el tabulador:

In [67]: frase.<TAB>
frase.capitalize  frase.isalnum     frase.lstrip      frase.splitlines
frase.center      frase.isalpha     frase.partition   frase.startswith
frase.count       frase.isdigit     frase.replace     frase.strip
frase.decode      frase.islower     frase.rfind       frase.swapcase
frase.encode      frase.isspace     frase.rindex      frase.title
frase.endswith    frase.istitle     frase.rjust       frase.translate
frase.expandtabs  frase.isupper     frase.rpartition  frase.upper
frase.find        frase.join        frase.rsplit      frase.zfill
frase.format      frase.ljust       frase.rstrip
frase.index       frase.lower       frase.split

Es así además como se aplican los método en Python, con la sintaxis variable.metodo. Aquí hay algunos ejemplos:

In  [70]: frase_mayusculas = frase.upper()    # Cambia a mayusculas y lo guardo en
In  [71]: print(frase_mayusculas)             # la variable frase_mayusculas
HOMBROS DE GIGANTES

In  [72]: frase_titulo = frase.title()        # Cambia la primera letra de cada palabra a mayuscula
                                              # y lo guarda en la variable frase_minusculas
In  [73]: print(frase_titulo)
Hombros De Gigantes

In  [74]: # Reemplaza una cadena de texto por otra
In  [75]: frase.replace("gigantes", "enanos")
'hombros de enanos'

Estos comandos devuelven una nueva cadena de texto cambiada, que vemos por pantalla, sin modificar la variable original, aunque no siempre es así. Podemos comprobar que la frase no se ha alterado de forma permanente haciendo print(frase).

Para cambiar la variable frase deberemos volver a definir la cadena de texto:

In  [76]: # Reemplaza definitivamente una cadena de texto por otra
In  [77]: frase = frase.replace("gigantes", "enanos")
In  [78]: print(frase)
'hombros de enanos'

Formato de texto

A menudo queremos imprimir texto con valores numéricos de algún tipo, pero esto no es posible porque string y números son tipos de datos distintos. La forma más básica de mezcar cadenas y números es convirtiendo los números a cadenas y concatenándolas:

In  [80]: a, b = 10, 10**2   # Definimos dos numeros, a=10 y b=10**2
In  [81]: print(str(a) + " elevado al cuadrado es " + str(b))
10 elevado al cuadrado es 100

En Python 3, la impresión es algo más flexible, poniendo un número indefinido de parámetros de distinto tipo separados por comas y print() los une con un espacio o con la cadena que le indiquemos en el parámetro sep, que es opcional:

In [80]: print(a, «elevado al cuadrado es», b) 10 elevado al cuadrado es 100

In [81]: print(a, » elevado al cuadrado es», b, sep=»;») 10;elevado al cuadrado es;100

Sin embargo, la manera más práctica y correcta de hacer esto es imprimiendo los números con el formato que queramos con la sintaxis de formato que Python hereda de C:

In [82]: # Calculamos el logaritmo base 10 de 2 e imprimimos
         # el resultado con 50 decimales
In [83]: print("%.50f" % log10(2.0**100))
30.10299956639811824743446777574717998504638671875000
In [84]: # Otro ejemplo usando texto, enteros y decimales
In [85]: print("El %s de %d es %f." % ('cubo', 10, 10.**3) )
El cubo de 10 es 1000.000000.

Aquí se reemplaza cada símbolo %s (para cadenas de texto), %d (para enteros) o %f (para floats) sucesivamente con los valores después de % que están entre paréntesis. En caso de los floats se puede utilizar el formato %10.5f, que significa imprimir 10 caracteres en total, incluído el punto, usando 5 decimales. El formato científico se emplea utilizando %e, por ejemplo:

In [86]: print("%.5e" % 0.0003567)
3.56700e-04

In [87]: # Otra forma de hacerlo (sin imprimir)
In [88]: resultado = format(0.0003567, ".5e")
In [89]: resultado
Out[89]: '3.56700e-04'  # es un string

Los formatos son muy útiles a la hora de expresar el resultado de un cálculo con los dígitos significativos solamente o con la indicación del error en el resultado. Así por ejemplo, si el resultado de un cálculo o de una medida es 3.1416 \(\pm\) 0.0001 podemos expresarlo como:

In [90]: # resultado de un cálculo obtenido con las cifras
In [91]: # decimales que proporciona el ordenador
In [92]: resultado = 3.1415785439847501
In [93]: # este es su error con igual número de cifras decimales
In [94]: error = 0.0001345610900435
In [95]: # así expresamos de forma correcta el resultado
In [96]: print("El resultado del experimento es %.4f +/- %.4f" % (resultado, error) )
El resultado del experimento es 3.1416 +/- 0.0001

En la línea 96 imprimimos dos valores, que deben darse entre paréntesis y separados por comas si es más de uno.

Además de esta sitaxis clásica, Python tiene un cuasi-lenguaje propio similar para dar formato a las cadenas usando el método format(). Este sistema propio de Python es más flexible y potente:

In [96]: print("{:.2f}".format(3.1415926))
3.14

Número

Formato

Salida

Descripción

3.1415926

{:.2f}

3.142

decimal places

3.1415926

{:+.2f}

3.142

decimal places with sign

-1

{:+.2f}

-12

decimal places with sign

2.71828

{:.0f}

3

No decimal places

5

{:0>2d}

5

Pad number with zeros (left padding, width 2)

5

{:x<4d}

5xxx

Pad number with x’s (right padding, width 4)

10

{:x<4d}

10xx

Pad number with x’s (right padding, width 4)

1000000

{:,}

1000000

Number format with comma separator

0.25

{:.2%}

25.00%

Format percentage

1000000000

{:.2e}

1000000000

Exponent notation

13

{:10d}

13

Right aligned (default, width 10)

13

{:<10d}

13

Left aligned (width 10)

13

{:^10d}

13

Center aligned (width 10)

Aquí hay algunos ejemplos de cómo se usan:

frase2 = "A hombros de {}".format("gigantes")
frase3 = " {0} es mejor que {1} ".format("Python", "IDL")
resultado = "El {operacion} de {numero} es {resultado}".format(operacion="cubo", numero=7, resultado=7**3)

# Devuelve:
A hombros de gigantes
Python es mejor que IDL
El cubo de 7 es 343

Esta especie de lenguaje tiene muchas más opciones, así que lo mejor es consultar la documentación oficial o alguna guía más detallada.

Estructuras de datos

Además de los tipos de datos univaluados, Python posee otros tipos de datos más complejos, las estructuras de datos. que permiten organizar y manipular varios elementos. El más común es la lista (list), que se crea con corchetes con los elementos separados por comas:

estrellas = ["Alhena", "Mizar", "Cor Caroli", "Nunki", "Sadr"]
datos = ["Beta pictoris", 1.6, [1, 2, -3]]

Con el método split() podemos separar un string en una lista de elementos, separando por defecto por espacios; se puede añadir un segundo parámetro opcional para separar por otro(s) caracter.

In [100]: palabras = frase.split()
In [101]: print(palabras)
['hombros', 'de', 'gigantes']

La listas se indexan prácticamente igual que las cadenas, donde cada elemento tiene un índice:

In [102]: estrellas[-1]   # El último elemento
Out[102]: 'Sadr'

In [103]: estrellas[1:3]  # Otra lista, del segundo (índice 1) al tercero (indice 2)
Out[103]: ['Mizar', 'Cor Caroli']

En el último ejemplo es importante darse cuenta que estrellas[1:3] no incluye el último elemento, de manera devuelve dos elementos; esto es fácil de prever restando el último del primero: 3-1 = 2 elementos.

Con el método range() tenemos un iterador de números enteros. Un iterador es un tipo de dato que posee métodos para iterar los elementos que contiene, que son similares a los de una lista. De hecho, en Python 2 la función range() no devuelve un iterador range, si no directamente una lista de enteros. En Python 3 podemos convertir el iterador range con list(range).

Admite hasta tres parámetros (ver help(range) o range?), aunque sólo uno es obligatorio, que es el número de elementos, que irá de 0 a ese número, sin incluirlo:

In [104]: # Iterador (range) de enteros de 0 a 4
In [105]: range(5)
Out[105]: range(0, 5)

In [106]: list(range(5))  # convierto el iterador a lista (no necesario en Python 2)
Out[106]: [0, 1, 2, 3, 4]

In [107]: # Lista de enteros de 10 a 15
In [108]: list(range(10, 16))
Out[108]: [10, 11, 12, 13, 14, 15]

In [108]: # Lista de enteros de 10 a 20 (el 21 no se incluye), de dos en dos
In [109]: list(range(10, 21, 2))
Out[109]: [10, 12, 14, 16, 18, 20]

Las listas tienen métodos similares a las cadenas de texto y también varios métodos para manipularlas y transformalas:

In [110]: len(estrellas)
Out[110]: 5

In [111]: estrellas.
estrellas.append   estrellas.index    estrellas.remove
estrellas.count    estrellas.insert   estrellas.reverse
estrellas.extend   estrellas.pop      estrellas.sort

In [112]: estrellas.append("Ras Algethi")  # Añado Ras Algethi al final de la lista

In [113]: print(estrellas)
['Alhena', 'Mizar', 'Cor Caroli', 'Nunki', 'Sadr', 'Ras Algethi']

In [114]: estrellas.insert(3, "Hamal")     # Añado Hamal en el cuarto lugar (índice 3)

In [115]: print(estrellas)
['Alhena', 'Mizar', 'Cor Caroli', 'Hamal', 'Nunki', 'Sadr', 'Ras Algethi']

In [116]: estrellas.pop()                 # Estraigo el último elemento (lo devuelve)
Out[116]: 'Ras Algethi'

In [117]: estrellas.pop(3)                # Estrae el elemento de índice 3
Out[117]: 'Hamal'

In [118]: estrellas.remove("Nunki")       # Elimina la primera ocurrencia de Hamal

In [119]: estrellas.sort()                # Ordena la lista original alfabéticamente

In [120]: estrellas
Out[120]: ['Alhena', 'Cor Caroli', 'Mizar', 'Sadr']

Aunque podemos manipular mucho las listas, existen funciones como map() o filter() que nos permite iterar con sus elementos; además el módulo itertools ofrece métodos adicionales para trabajar con listas y otros iterables. Veremos algunos ejemplos más adelante.

Otro tipo de dato estructurado son las tuplas, que básicamente son listas inalterables. Tienen propiedades de indexado similares, pero no poseen métodos para modificarlos porque no se pueden cambiar. Se declaran entre paréntesis en lugar de corchetes y se suelen usar en parámetros de funciones y datos que no se suelen modificar. Cuando definimos una serie de datos de cualquier tipo separados por comas, se considera una tupla, aunque no estén entre paréntesis.

In [122]: c = (1, 3)                  # Defino una tupla
In [123]: print(c)
(1, 3)
In [124] parametros = 4, 5, 2, -1, 0  # Creamos otra tupla al indicar varios datos separados
                                      # por comas, aunque no estén entre paréntesis
In [125] type(parametros)
<type 'tuple'>

Los diccionarios son estructuras que en lugar de índices numéricos tienen índices de cadenas declarados por nosotros, lo que es muy útil para describir propiedades.

In [128] star = {'name': 'Hamal', 'mag': 2.0, 'SpT': 'K2III', 'dist': 66}
In [129] type(star)
Out[129] <type 'dict'>

En este caso hemos creado una clave «name» con valor «Hamal», otra clave «mag» con valor 2.0, etc. Al crear los datos con esta estructura, podemos acceder a los valores de las claves fácilmente, así como definir nuevas parejas clave-valor:

In [130]: print(star['name'])
Hamal

In [131]: star['parallax'] =  49.56    # Añadimos un nuevo dato

Hay que notar que ya no podemos acceder a los datos por su índice, ya que los diccionarios se indexan exclusivamente por la clave y obtenendremos un error si lo hacemos. Además, el orden de los elementos no será necesariamente el con el que lo hemos creado, porque éste se pierde al carecer de índices numéricos:

In [134]: print(star[0])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 0

In [135]: print(star)  # demonios, no estan en el orden original!
{'dist': 66, 'SpT': 'K2III', 'name': 'Hamal', 'mag': 2.0}

Podemos conocer todas las claves y los valores de un diccionario usando los métodos keys() y values() respectivamente:

In [135]: star.keys()
Out[135]:['dist', 'SpT', 'name', 'mag']

In [136]: star.values()
Out[136]: [66, 'K2III', 'Hamal', 2.0]

Finalmente, también existen los set que se podrían llamar conjuntos, que son colecciones sin ordenar de elementos no duplicados. Son útiles probar pertenencias a listas eliminar entradas duplicadas, además de tener operaciones matemáticas como unión, intersección, etc.

In [140]: estrellas1 = set( ("Alhena", "Mizar", "Cor Caroli") )
In [141]: estrellas2 = set( ("Mizar", "Cor Caroli", "Nunki", "Sadr") )

In [141]: "Alhena" in estrellas1    # Esto también funciona con listas y tuplas
Out[141]: True

In [142]: "Alhena" in estrellas2
Out[142]: False

In [143]: # Union de dos conjuntos
In [144]: estrellas1 | estrellas2
Out[144]: {'Alhena', 'Cor Caroli', 'Mizar', 'Nunki', 'Sadr'}

In [145]: # Intersección, elementos comunes
In [146]: estrellas1 & estrellas2
Out[147]: {'Cor Caroli', 'Mizar'}

In [148]: # Diferencia, los que están en estrellas1 pero no en estrellas2
In [149]: estrellas1 - estrellas2
Out[149]: {'Alhena'}

In [150]: # Diferencia simétrica, que son únicos en cada conjunto
In [150]: estrellas1 ^ estrellas2
Out[151]: {'Alhena', 'Nunki', 'Sadr'}

Módulos de Python

Al iniciar la terminal de Python tenemos disponibles de inmediato todos los tipos de datos y funciones que hemos visto hasta ahora, pero podemos tener funcionalidades adicionales importado módulos de Python, ya sean de la librería estándar o externo. Por ejemplo, si queremos usar funciones matemáticas básicas debemos importar el módulo math de la librería estándar de Python para poder usarlas:

In [160]: import math
In [161]: print(math.sin(0.5*math.pi))
1.0
In [162]: dir(math)
['__doc__', '__name__', '__package__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh',
 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs',
 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'hypot', 'isinf', 'isnan', 'ldexp',
 'lgamma', 'log', 'log10', 'log1p', 'modf', 'pi', 'pow', 'radians', 'sin',
 'sinh', 'sqrt', 'tan', 'tanh', 'trunc']

In [164]: a, b = 4.4, 4.5
In [165]: print(math.ceil(a)), (math.ceil(b))
Out[165]: 5.0 5.0

In [166]: print(floor(a)), (math.floor(b))
Out[166]: 4.0 4.0

Arriba usamos dir() para ver el contenido del módulo, sus métodos y propiedades disponibles, que no son más que la librería matemática estándar de C. Además del listado, podemos obtener ayuda de un módulo o método (función) usando el comando help(). Más adelante veremos cómo funcionan los módulos y paquetes en Python.

Programas ejecutables

Hasta ahora hemos trabajado iterativamente con la terminal de Python, pero podemos crear programas ejecutables más complejos con cualquier editor de código. Prácticamente cualquier editor clásico como Emacs, Vim, Kate, etc. nos servirá, pero es muy recomendable usar el IDE Spyder, específicamente hecho para Python y con muchas ventajas.

Veamos un ejemplo sencillo de un programa ejecutable:

#!/usr/bin/python3
#-*- coding: utf-8 -*-

# Mensaje de bienvenida
print("Programa de calculo del cubo de un numero.\n")

# Numero de entrada
x = 23.0

# Calculo el valor del cubo de x
y = x**3

# Imprimo el resultado
print("El cubo de {:.2f} es {:.2f}".format(x, y))

El programa se puede ejecutar ahora desde una consola desde el directorio en donde tenemos el archivo con el programa, escribiendo en ella:

$ python3 cubo.py

y la respuesta del programa será:

Programa de calculo del cubo de un numero.

El cubo de 23.00 es 12167.00

En la primera línea del programa hemos escrito #!/usr/bin/python3 para indicar la ruta donde tenemos instalado python. La segunda línea, #-\ *- coding: utf-8 -*- hemos indicado el tipo de codificación UTF-8, para poder poner caracteres especiales como tildes y eñes.

Ya que la primera línea indica en qué directorio está el ejecutable de python, el programa también se puede ejecutar como un comando o programa del sistema, escribiendo únicamente:

$ ./cubo.py

Si quieres ejecutar el programa desde dentro de la consola estándar de python, puedes usar lo execfile():

>>> execfile('cubo.py')

de igual manera, si estamos usando IPython podemos usar el comando mágico %run, que en fondo es una llamada a execfile():

%run cubo.py

La ventaja en este último caso con IPython es que una vez ejecutado, las variables y funciones definidas en el programa lo estarán ahora lo estarán en la sesión de IPython, lo que es muy útil para probar y corregir nuestro programa interactivamente.

print(x)
23.0

es decir, vemos ahora tenemos la variable x está definida en nuestra sesión.

Podemos definir funciones reutilizables con el comando def(), por ejemplo, para usarlo en nuestro programa anterior:

# Definimos una función que calcula el cubo de un número cualquiera
def cubo(x):
    y = x**3
    return y

# Utilizamos la funcion para calcular el cubo de 4
resultado = cubo(4.0)

Ahora podemos llamar a la función cubo() cuando queramos.

¡ALTO!: Aquí ha pasado algo importante. Fíjense en la definición de cubo(x) ¿cómo sabe Python donde empieza y termina la función si no hay llaves que abren ni cierran o palabras clave del tipo «DO BEGIN» o «END» como en otros lenguajes? La clave está en el sangrado (identación), que es obligatorio en Python ya que es como se indican dónde empiezan y terminan los bloques de código. No hay número específico de espacios que haya que poner (pero lo recomendado es cuatro), lo importante es que las líneas estén en el mismo bloque de sangrado. En nuestro ejemplo sencillo, la función empieza después de «:» y termina donde el margen vuelve a ser el original.

También es fundamental no mezclar espacios y tabuladores, porque puede producir errores de sangrado (indentation error), por lo que se recomendable configurar el editor de manera que escriba siempre espacios al pulsar el tabulador.

En Python, la variables definidas dentro de una función son locales, lo que quiere decir que sólo están definidas dentro de la función y no están accesibles fuera. Podemos comprobar esto con una pequeña modificación del programa:

In [168]: def cubo(x):
    ...:     potencia = 3
    ...:     return x**potencia
    ...:

In [169]: cubo(3.5)
Out[169]: 42.875

In [170]: print(potencia)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-7baa8b437297> in <module>()
----> 1 potencia

NameError: name 'potencia' is not defined

Como vemos, aunque función funciona correctamente, la variable potencia que definimos dentro no existe fuera de la función. Esto igualmente cierto para funciones definidas dentro de funciones. Cuando creamos variables y funciones fuera de una función, como hecho hasta ahora, son todas globales.

Además de sentencia de def() que crea funciones ordinarias, existen la funciones anónimas o función lambda que permiten definir funciones simples en una sola línea:

cubo = lambda x: x**3

cubo(3)

El comando def(), éste permite varios parámetros en entrada, que pueden ser opciones si se incluye un valor por defecto:

def saludo(nombre, apellido=""):
    print "Hola {} {}".format(nombre, apellido)

saludo('Carmen')
Hola Carmen

Los valores de los parámetros se asignan en el orden en el que están definidos en la función, pero si se usan con su nombre, pueden ponerse en cualquier orden. Por ejemplo:

saludo(apellido="Prieto", nombre="Gloria")
Hola Gloria Prieto

Es posible tener un número indeterminado de parámetros, dados en forma de tupla o diccionario, en cuyo caso en la definición los parámetros deben empezar con * o ** respectivamente:

def datos_estrella(*args, **kwargs):
    print("Nombre", args)
    print("Datos", kwargs)


datos_estrella('Altair', mag=2.2, SpT="A7V")
Nombre ('Altair',)
Datos {'SpT': 'A7V', 'mag': 2.2}

En este ejemplo vemos que la función datos_estrella() admite un número indefinido de parámetros sin nombre, que se agruparán en una lista llamada args y también un número indefinido de parámetros con nombre (con parejas clave=valor), que se agruparán en una lista llamada kwargs en nuestro ejemplo.

Uso de funciones con iterables

Podemos usar funciones con tipos de datos basicos como strings o float, pero no podemos aplicarlos directamente a listas o tuplas. Por ejemplo, no podemos hacer cubo(range(5)) esperando obtener una lista de resultado. Si embargo, podemos usar algunos métodos como map() o filter() para operar recursivamente con iterables, como mencionamos antes. Veamos este ejemplo:

n = range(10)

def raiz(x):
    return x**0.5

# Aplica una función a un iterable
resultado = map(raiz, n)  # Devuelve una lista (python 2) o iterable (python 3)

# Devuelve una lista (python 2) o iterable (python 3) para los elementos
# del iterable que son True
mayores5 = filter((lambda x: x >=5), n)

Entrada de datos en programas

Los programas ejecutables que creamos necesitan a menudo datos de entrada que pueden ser distintos cada vez que se usan, e incluirlos directamente como variables dentro del código puede no ser muy eficiente ya que tendríamos que abrir y modificar el fichero cada vez que cambiamos los parámetros.

Podemos usar la función input() (raw_input() en Python 2) para pedir entradas por teclado al usuario. Modificamos nuestro pequeño programa para usarlo:

# Numero de entrada
x = input('Dame un numero: ')

x = float(x)

resultado = cubo(x)

print("El cubo de {} es {}".format(x, resultado))

Sin embargo, input() devuelve siempre un string aunque se de un valor numérico, por eso hay que transformarlo antes a entero o float para operar con el. Una alternativa a convertir strings en valores numéricos es emplear la función eval(), que evalúa un string como si fuese código y devuelve el resultado, incluyendo una lista si el string contiene comas:

x = 3.1416
eval("x**3")  # Hace la operación y devuelve un float
31.006494199296

eval("10, 20, 30")  # Si es un string con comas devuelve una tupla
(10, 20, 30)

Una manera alternativa de dar parámetros de entrada a un programa es usar argumentos justo después de la llamada del programa, para lo que necesitamos la función argv del módulo sys. Se usa de la siguiente forma:

from sys import argv

# Los parámetros pasados están en una lista de strings en argv, en la que
# el primer elemento es el nombre del programa y el segundo el primer parámetro
x = argv[1]

x = float(x)

resultado = cubo(x)

Ahora podemos llamar al programa poniendo los parámetros después:

# Para calcular el cubo de 6.5
./cubo.py 6.5

Se puede añadir un número indefinido de parámetros, pero como vemos debe hacerse sin nombres.

Una opción más sofisticada, pero también algo más compleja, es emplear el módulo argparse, que permite definir parámetros de entrada complejos con varias opciones. Consideremos un programa que puede calcular la raíz o el cubo de un número, según decida el usuario. Podemos hacerlo de la siguiente manera:

import argparse
from math import sqrt

parser = argparse.ArgumentParser()

# Dos argumentos obligatorios posibles, el numero a calcular
# y la operacion a realizar
parser.add_argument("-n", "--numero", help="Numero a calcular",
                    type=float, required=True)

parser.add_argument("-o", "--oper", help="Operacion a realizar: cubo o raiz",
                    type=str, default='cubo')

args = parser.parse_args()

# Funciones de las operaciones que puede hacer el programa
def cubo(x):
    y = x**3
    return y

def raiz(x):
    return sqrt(x)

# Hacemos una operación u otra según la opcion elegida por el usuario
if args.oper == 'cubo':
    print("El cubo de {0} es {1}".format(args.numero, cubo(args.numero)))
elif args.oper == 'raiz':
    print("La raiz de {0} es {1}".format(args.numero, raiz(args.numero)))
else:
    print("Error, operacion desconocida")

Ahora podemos añadir argumentos durante la ejecución del programa y además genera automáticamente un mensaje de ayuda con los argumentos disponibles usando --help.

./cubo-argparse.py --help
usage: cubo-argparse.py [-h] [-n NUMERO] [-o OPER]

optional arguments:
  -h, --help            show this help message and exit
  -n NUMERO, --numero NUMERO
                        Numero a calcular
  -o OPER, --oper OPER  Operacion a realizar: cubo o raiz


./cubo-argparse.py -n 10 -o cubo
El cubo de 10.0 es 1000.0

./cubo-argparse.py -n 10 --oper raiz
La raiz de 10.0 es 3.16227766017

Control de flujo

Python tiene los elementos de control de flujo más comunes: if-then-else, for y while, con la peculiaridad que se usan los bloques de sangrado para delimitarlos. El bucle for se suele usar para recorrer iterables (listas, tuplas, etc.), deteniéndose cuando se agota la lista:

for nombre in estrellas:
    print(nombre)

Alhena
Cor Caroli
Mizar
Sadr

Alternativamente, podemos recorrer una lista de índices y llamar a cada elemento con su índice:

for i in range( len(estrellas) ):
    print("{0}) {1}. ".format(i+1, estrellas[i]))

1) Alhena.
2) Cor Caroli.
3) Mizar.
4) Sadr.

Si estamos trabajando con un diccionario, no podemos hacer esto directamente porque no tienen índices numéricos, pero podemos emplear el método items() (iteritems() en Python 2) para iterar parejas clave:valor:

for clave, valor in star.items():
    print("{0}: {1}".format(clave, valor))

dist: 66
SpT: K2III
name: Hamal
mag: 2.0

En este ejemplo tenemos que usar dos variables mudas (parejas clave y valor) en lugar de una para capturar cada clave y valor del diccionario.

Los bucles for nos permiten crear nuevas lístas dinámicamente:

# Importamos la función log10 del módulo math
from math import log10

b = [2.3, 4.6, 7.5, 10.]

c = [log10(x) for x in b]

print(c)
# Resultado:
# [0.36172783601759284, 0.66275783168157409, 0.87506126339170009, 1.0]

¡Momento! ¿Qué pasaría si en la lista de números b está el 0?

In [78]: b = [0, 2.3, 4.6, 7.5, 10.]

In [79]: c = [log10(x) for x in b]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-79-bcb6e1180ea3> in <module>()
----> 1 c = [log10(x) for x in b]

ValueError: math domain error

Desde luego que da un error ¿qué hacemos? Aquí está la flexibilidad de Python:

c = [log10(x) for x in b if x > 0]

El bucle while repite una serie de órdenes mientras una condición sea cierta (vale True):

cuentas = 0

while cuentas < 6:
   print(cuentas)
   cuentas = cuentas + 1

De manera similar poder hacer que while funcione mientras no se cumpla una condición usando while not:

x = 0
while not x == 5:
  x = x + 1
  print("x = %d" % x)

 """ Resultado que obtenemos del programa:
 x = 1
 x = 2
 x = 3
 x = 4
 x = 5
 """

En el ejemplo anterior hemos hecho una comparación de igualdad x == 5 usando enteros, pero hay que tener cuidado cuando se comparan mediante una igualdad exacta números decimales o de coma flotante floats entre sí. Debido a la precisión finita de los ordenadores, es posible que una determinada igualdad nunca se cumpla exactamente y por lo tanto la ejecución del bucle nunca se detendrá. Podemos comprobar esto con un ejemplo en el que imprimimos los números que van de 0.0 a 1.0 a intervalos de 0.1,:

x = 0.0
# Mientras x no sea exactamente 1.0, suma 0.1 a la variable *x*
while not x == 1.0:
    x = x + 0.1
    print("x = {:19.17f}".format(x))

""" Resultado que obtenemos:
x = 0.10000000000000001
x = 0.20000000000000001
x = 0.30000000000000004
x = 0.40000000000000002
x = 0.50000000000000000
x = 0.59999999999999998
x = 0.69999999999999996
x = 0.79999999999999993
x = 0.89999999999999991
x = 0.99999999999999989  <-- El bucle while debió detenerse aquí, pero no lo hizo
x = 1.09999999999999987
x = 1.19999999999999996
x = 1.30000000000000004
  .
  .
  .                      <-- Presionar Ctrl+C para detener el programa
"""

y así el bucle no se para nunca. El código anterior produce un bucle infinito porque la condición x == 1.0 nunca se da exactamente (el valor más cercano es 0.99999999999999989 pero no es 1.0). La conclusión que podemos extraer de aquí es que es preferible no comparar nunca variables o números de tipo float exactamente.

Una opción para resolver el problema anterior es usar rangos de precisión en el que definimos lo que para nosotros es suficientemente cercano al valor deseado. Así, con el ejemplo anterior podríamos hacer:

x = 0.0
# Condición de que nuestro numero se acerque a 1.0
# al menos en 1e-8
while abs(x - 1.0) > 1e-8:
    x = x + 0.1
    print("x = %19.17f" % x)

""" Resultado que obtenemos:
x = 0.10000000000000001
x = 0.20000000000000001
x = 0.30000000000000004
x = 0.40000000000000002
x = 0.50000000000000000
x = 0.59999999999999998
x = 0.69999999999999996
x = 0.79999999999999993
x = 0.89999999999999991
x = 0.99999999999999989
"""

Finalmente, el if-then-else funciona de forma habitual, siendo la sentencia if la única obligatoria, pudiendo poner indefinidos if alternativos con elif y finalizar opcionalmente con else. Veamos un ejemplo simple:

# un numero a valorar
c = 12

if c > 0:         # comprueba si es positivo
   print("La variable c es positiva")
elif c < 0:       # si no lo es, comprueba si es negativo
   print("La variable c es negativa")
else:             # Si nada de lo anterior se cumple, haz lo siguiente
   print("La variable c vale 0")

Si primer bloque con if se cumple (c > 0 es True), se ejecuta ese bloque en if y únicamente ese bloque. Si no se cumple (c > 0 es False), se comprueban sucesivamente el resto de elif, ejecutándose solo el primero sea True. Si tanto el if como los elif han devuelto False, se ejecuta el bloque else.

Existen algunos elementos adicionales que podemos usar en las secuencias de control de flujo. La sentencia break permite interrumpir el bloque más cercano en un bucle while o for. De manera similar, continue continúa con la siguiente iteración dentro del bucle más cercano. La sentencia else en bucles permite ejecutar una acción cuando el bucle termina una lista (en bucles for) o cuando la condición es falsa (con while) pero no si el bucle se interrumpe usando break. Veamos el ejemplo de un programa que calcula los números primos entre 2 y 10:

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print("{} es igual a {}*{}.".format(n, x, n/x))
            break  # cortamos el bucle for
    else:
        # El bucle termina sin encontrar factor
        print("{} es numero primo.".format(n))

""" Imprime:
2 es numero primo.
3 es numero primo.
4 es igual a 2*2.
5 es numero primo.
6 es igual a 2*3.
7 es numero primo.
8 es igual a 2*4.
9 es igual a 3*3.
"""

En este ejemplo usamos break para cortar el bucle for más interno si el if se cumple (es True) y así evitar que muestre multiplicaciones equivalentes (e.g.: 3*4 = 4*3); podemos comprobar lo que ocurriría si no pusiésemos break. Es útil para por ejemplo, evitar que un bucle siga ejecutándose si ya se cumplió la condición.

Algo muy interesante en este ejemplo es que usamos la sentencia else con for, en lugar de usarla con if como es habitual. Un else en for se ejecuta cuando la lista en el ``for`` llega al final, sin cortarse. De esta manera imprimimos un aviso si el bucle termina (el for agota la lista) sin llegar a usar break, lo que indica que ningún número de la lista es múltiplo suyo y por tanto es primo.

La sentencia continue indica continuar con el siguiente elemento del bucle más interior, interrumpiendo el ciclo actual. Veamos un ejemplo que comprueba qué números son mayores que 4 en una lista de número creciente, de 0 a 7:

for k in range(8):
    if k > 4:
        print("%d es mayor que 4." % k)
        continue
    print("%d es menor o igual que 4." % k)

0 es menor o igual que 4.
1 es menor o igual que 4.
2 es menor o igual que 4.
3 es menor o igual que 4.
4 es menor o igual que 4.
5 es mayor que 4.
6 es mayor que 4.
7 es mayor que 4.

Es este caso, con continue evitamos que se ejecute el último print() si k > 4, continuando el bucle con el siguiente elemento de la lista. Así, si llegamos a k > 4 es True, es evidente que el siguiente elemento de la lista también lo cumplirá y se puede continuar con el siguiente elemento de for más cercano. Desde luego que pudimos haber hecho un código similar usando una sentencia if-else, pero resultaría más complejo.

Ejercicios

  1. La variación de temperatura de un cuerpo a temperatura inicial \(T_0\) en un ambiente a \(T_s\) cambia de la siguiente manera:

    \[T = T_s + (T_0 - T_s) \ \ e^{-kt}\]

    con t en horas y siendo k un parámetro que depende del cuerpo (usemos k=0.45). Una lata de refresco a 5ºC queda en la guantera del coche a 40ºC. ¿Qué temperatura tendrá 1, 5, 12 y 14 horas? Encontrar las horas que hay que esperar para que el cuerpo esté a 0.5ºC menos que la temperatura ambiente. Definir funciones adecuadas para realizar ambos cálculos para cualquier tiempo y cualquier diferencia de temperatura respecto al ambiente respectivamente.

  2. Para el cálculo de la letra del DNI se calcula el residuo 23 del número, es decir, el resto que se obtiene de la división entera del número del DNI entre 23. El resultado será siempre un valor entre 0 y 22 y cada uno de ellos tiene asignado una letra según la siguiente tabla:

    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

    T R W A G M Y F P D  X  B  N  J  Z  S  Q  V  H  L  C  K  E

    Escribir un programa que solicite el número de DNI al usuario y calcule la letra que le corresponde.

  3. Obtener un valor de \(\pi\) calculando la suma siguiente para n=200:

    \[4 \sum^n_{k=1} \frac{(-1)^{k+1}}{2k - 1}\]
  4. Se llama sucesión de Fibonacci a la colección de n números para la que el primer elemento es cero, el segundo 1 y el resto es la suma de los dos anteriores. Por ejemplo, la sucesión para n=5 es (0, 1, 1, 2, 3). Crear un programa que calcule la lista de números para cualquier n.

  5. Genera una lista que contenga el cuadrado de los números pares y el cubo de los impares entre 1 y 100 (inclusive). Calcula cuantos números de esa lista debes sumar para que el resultado de la suma sea lo más cercano posible, pero inferior, a un millón.

  6. Escribir un programa que proporcione el desglose en el número mínimo de billetes y monedas de una cantidad entera cualquiera de euros dada. Recuerden que los billetes y monedas de uso legal disponibles hasta 1 euro son de: 500, 200, 100, 50, 20, 10, 5, 2 y 1 euros. Para ello deben solicitar al usuario un número entero, debiendo comprobar que así se lo ofrece y desglosar tal cantidad en el número mínimo de billetes y monedas que el programa escribirá finalmente en pantalla.