Texto y código sujeto bajo Creative Commons Attribution license, CC-BY-SA. (c) Original por Lorena A. Barba y Gilbert Forsyth en 2013, traducido por F.J. Navarro-Brull para CAChemE.org

Python Crash Course

¡Hola! Esto es una introducción rápida de programación en Python para ayudarte a comenzar a trabajar con el curso llamado 12 pasos para Navier-Stokes.

Librerías (Libraries)

Python es un lenguaje de alto nivel cuya filosofía hace hincapié en una sintaxis muy limpia y que favorezca un código legible. Una de las grandes ventajas de Python es que la gran mayoría de código es abierto. Pero el Ecosistema de Python está habitado por muchas bibliotecas o librerías que proporcionan herramientas útiles, como las operaciones matriciales (arrays), representaciones gráficas, etc. Podemos importar librerías de funciones para expandir las capacidades de Python en nuestros programas. ¡Ok! Vamos a empezar con la importación de algunas de ellas para ayudarnos.

In [3]:
# Los comentarios en python se establecen con almohadillas

import numpy as np
# numpy es una libería que se esta importando y permite realizar operaciones matriciales estilo MATLAB.
# Para acceder a sus funciones se hará uso de una abreviatura mediante la siguiente nomenclatura "np.NombreDeLaFuncion"

import matplotlib.pyplot as plt 
# matplotlib es una librería para dibujar gráficas en 2D y 3D que se usará para representar los resultados
# Accedemos al módulo pyplot dentro de la biblioteca matplotlib. 
# pyplot es un módulo que ofrece una interfaz de gráficos similar a la de MATLAB
# En este caso se accedera a la funciones como "plt.NombreDeLaFuncion"

En resumen, ¿qué es esto de import X as x? Mediante estos comandos, se está importando la librería (library) llamada numpy y un sub-librería o módulo de un paquete llamado matplotlib. Debido a que las funciones que queremos usar pertenecen a estas librerías, se debe indicar a Python de donde pertenecen cuando se llamen. Con las dos líneas superiores se han creado accesos directos a dichas librerías como np y plt, respectivamente. Por tanto, si se requiere el uso de la función linspace contenida dentro de la biblioteca numpy, por ejemplo, se puede llamar de la siguiente forma:

In [4]:
myarray = np.linspace(0, 5, 10)
myarray
Out[4]:
array([ 0.        ,  0.55555556,  1.11111111,  1.66666667,  2.22222222,
        2.77777778,  3.33333333,  3.88888889,  4.44444444,  5.        ])

Si no se indica previamente a la función linspace con np, Python dará un error.

In [5]:
myarray = linspace(0, 5, 10)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-ed3ba806937a> in <module>()
----> 1 myarray = linspace(0, 5, 10)

NameError: name 'linspace' is not defined

En ocasiones, se puede observar código que importa la librería directamente sin usar una abreviatura para ello (como np para numpy). Aunque el acceso a las funciones será directo y ahorraría tiempo a la hora de escribir código, no es recomendable ya que lleva a errores posteriores. ¡Mejor adquirir buenas costumbres de programación desde un principio!

Para aprender las funciones que NumPy proporciona, puedes visitar la siguiente página NumPy Reference. Si ya sabes programar en MATLAB, hay una página que te será de ayuda: NumPy for Matlab Users.

También es recomendable echar un vistazo a los primeros capítulos de Introducción a Python para ingenieros de Guillem Borrell

Por último, se puede aprender a usar el módulo pyplot de la librería matplotlib a partir del material generado por la gente de Pybonacci.

Variables

Python no requiere declarar explicitamente las variables tal y como ocurre en C y otros lenguajes de programación.

In [1]:
a = 5      # a es el número entero (integer) 5
b = 'five' # b es una cadena de caracteres (string) con la palabra 'five'
c = 5.0    # c es el número de coma flotante (floating point) 5  
In [2]:
type(a)
Out[2]:
builtins.int
In [3]:
type(b)
Out[3]:
builtins.str
In [4]:
type(c)
Out[4]:
builtins.float

Hay que llevar especial cuidado y asignar a las variables los números en coma flotante o se podrán obtener valores no esperados en los programas. Por ejemplo:

In [7]:
14/a
Out[7]:
2
In [6]:
14/c
Out[6]:
2.8

Si se divide un número entero por otro entero, el resultado será redondeado al entero más próximo de la solución. Si se desea obtener una respuesta en coma flotante, uno de los números envueltos en la operación debe de serlo. Para ello, un truco es añádir un punto al final de un número entero. Es importante no confundir esta notación con las operaciones elemento por elemento tal y como hace MATLAB.

In [12]:
14./a 
Out[12]:
2.8

Si se trabaja con las últimas versiones de Python (a partir de la 3) esto deja de ocurrir ya que la división será tratada tal y como estamos acostumbrados. Para cambiar el comportamiento de la división en Python 2.7 se puede hacer uso de:

In [13]:
from __future__ import division

Mediante esta línea la división de dos números enteros dará decimales si la solución los tiene.

In [14]:
14/a
Out[14]:
2.8

Espacios (whitespace) en Python

Python usa tabulaciones (indents) y espacios (whitespace) para agrupar el contenido de una estructura de control (for, while, if...). En realidad, el tabulado se compone por cuatro espacios en blanco y obliga a escribir código de forma ordenada. Un programa sin tabular correctamente nos devolverá un IndentationError. Este hecho permite además cerrar sentencias sin el uso de "end" lo que ayuda a la claridad del código de forma nativa. Por ejemplo, para escribir un bucle (loop) en C, se usaría:

for (i = 0, i < 5, i++){
   printf("Hola! \n");
}

Python, no usa llaves "{}" (curly braces) como C en el ejemplo superior, por lo que el mismo programa escrito en Python quedaría como:

In [8]:
for i in range(5):
    print "¡Hola! \n"
¡Hola! 

¡Hola! 

¡Hola! 

¡Hola! 

¡Hola! 

Si se tienen bucles anidados (nested for-loops) o unas estructuras de control dentro de otras, se debe añadir un tabulado en el interior. Por ejemplo para dos bucles for:

In [12]:
for i in range(3):
    for j in range(3):
        print i, j
    
    print "Esta sentencia pertenece al bucle 'i', pero no al bucle 'j'"
0 0
0 1
0 2
Esta sentencia pertenece al bucle 'i', pero no al bucle 'j'
1 0
1 1
1 2
Esta sentencia pertenece al bucle 'i', pero no al bucle 'j'
2 0
2 1
2 2
Esta sentencia pertenece al bucle 'i', pero no al bucle 'j'

Nota: Por si no lo sabías, la \n al final indica que que se escriba una nueva línea. ¡Ah!, si estás usando una versión más reciente de Python (3.x), print se tratará siempre como si fuera una función: print("¡Hola!")

Trabajando con arrays

Un array es un conjunto de valores con el mismo tipo, esto es, todos sus elementos son enteros, reales en doble precisión, complejos en simple precisión...

La clase array es más parecida a los arrays que se encuentran en C o en Fortran que a las matrices de MATLAB. Es importante destacar que todas las operaciones aritméticas entre arrays se ejecutan elemento a elemento y no como multiplicaciones matriciales tal y como ocurre en MATLAB.

En NumPy, se pueden seleccionar valores de arrays de forma similar que en MATLAB. Creamos un array con valores de 1 a 5:

In [18]:
myvals = np.array([1, 2, 3, 4, 5])
myvals
Out[18]:
array([1, 2, 3, 4, 5])

Python indexa con la numeración a partir de cero (zero-based index), asi que vamos a ver el primero y último elemento en el array myvals:

In [20]:
myvals[0], myvals[4]
Out[20]:
(1, 5)

Hay 5 elementos en el array myvals, pero si tratamos de ver el myvals[5], Python se quejará ya que myvals[5] está llamando a un 6º elemento que no existe en este array.

In [20]:
myvals[5]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-20-6cc4d3ae83cd> in <module>()
----> 1 myvals[5]

IndexError: index 5 is out of bounds for axis 0 with size 5

Los arrays tambien pueden ser divididos o cortados (sliced) seleccionando un rango de valores. Veamos cómo seleccionar los tres primeros elementos:

In [22]:
myvals[0:3]
Out[22]:
array([1, 2, 3])

Fíjate que la selección de elementos incluye el valor inicial y excluye el final. El comando de arriba devuelve los valores de myvals[0], myvals[1] y myvals[2], pero no de myvals[3].

Python posibilita la utilización de índices negativos para numerar una secuencia desde el final. De esta forma, el elemento correspondiente al índice -1 será el último elemento de la secuencia

In [19]:
myvals[-1]
Out[19]:
5

Asignando variables con array

Una de los capricho-funcionalidades en Python que suele confundir a la gente es la asignación y comparación de arrays de valores. Por ejemplo, definamos un array de una dimensión (1-D) llamado $a$:

In [33]:
a = np.linspace(1,5,5)
In [34]:
a
Out[34]:
array([ 1.,  2.,  3.,  4.,  5.])

OK, entonces tenemos un array $a$, con los valores del 1 al 5. Si quiero hacer una copia de este array, llamado $b$, haré lo siguiente:

In [35]:
b = a
In [36]:
b
Out[36]:
array([ 1.,  2.,  3.,  4.,  5.])

Genial. Entonces $a$ tiene los valores de 1 a 5 tal y como tengo ahora para $b$. Ya que tengo una copia de $a$, puedo cambiar sus valores sin preocuparme de perder datos (¡o eso puedo pensar!)

In [37]:
a[2] = 17
In [38]:
a
Out[38]:
array([  1.,   2.,  17.,   4.,   5.])

Aquí el 3º elemento de $a$ se ha cambiado a 17. Vamos ahora a comprobar que ha pasado con $b$.

In [39]:
b
Out[39]:
array([  1.,   2.,  17.,   4.,   5.])

¡Y aquí es donde las cosas fallan! Cuando se usa una declaración del tipo $a = b$, más que copiar todos los valores de $a$ a un nuevo array llamado $b$, Python simplemente crea un alias (o puntero) llamado $b$ y lo referencia con $a$. Por lo que si cambiamos el valor en $a$ entonces $b$ reflejará ese cambio (técnicamente, esto se llama assignment by reference). Si se quiere realizar una copia real de un array, se tiene que decir a Python que copie todos los elementos de $a$ en un nuevo array. Llamémosle $c$.

In [58]:
c[:] = a[:]

Desafortundamente, si queremos hacer una copia real, los nuevos arrays deben de ser definidos primero y deben de tener el mismo número de elementos. Por lo que será un proceso con dos pasos. Se puede definir un array "vacío" que sea de las mismas dimensiones que $a$ usando la funcion de numpy empty_like:

In [59]:
c = np.empty_like(a)
In [60]:
len(c) # muestra la longitud de c
Out[60]:
5
In [61]:
c[:]=a[:] # copia los elementos de a en c
In [62]:
c
Out[62]:
array([  1.,  13.,  17.,   4.,   5.])

Ahora, podemos probar de nuevo a cambiar un valor de $a$ y ver si también cambia el valor en $c$.

In [63]:
a[2] = 3
In [64]:
a
Out[64]:
array([  1.,  13.,   3.,   4.,   5.])
In [65]:
c
Out[65]:
array([  1.,  13.,  17.,   4.,   5.])

¡Funcionó! Si la diferencia entre a = b y a[:]=b[:] no está clara, deberías leer este apartado de nuevo. Esta cuestión volverá a aparecer más adelante para ponerte a prueba.

NOTA : Existe una forma más sencilla de copiar arrays en NumPy y es mediante la función copy()

In [72]:
a = np.linspace(1,5,5)
d = np.copy(a)
In [73]:
d
Out[73]:
array([ 1.,  2.,  3.,  4.,  5.])
In [74]:
a[2] = 17
In [75]:
a
Out[75]:
array([  1.,   2.,  17.,   4.,   5.])
In [76]:
d
Out[76]:
array([ 1.,  2.,  3.,  4.,  5.])

Más información.

Hay muchos recursos online para aprender más sobre NumPy y otras librerías. Sólo por diversión, vamos a usar una funcionalidad de IPython para incluir un vídeo de YouTube en este notebook.

In [41]:
from IPython.display import YouTubeVideo
# Un pequeño vídeo en inglés de como trabajar con arrays en NumPy arrays, de Enthought
YouTubeVideo('vWkb7VahaXQ')
Out[41]:
In [2]:
from IPython.core.display import HTML
def css_styling():
    styles = open("../styles/custom.css", "r").read()
    return HTML(styles)
css_styling()
Out[2]: