Primeros pasos

Sobre la filosofía de programación en Python...

In [1]:
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Los comentarios se marcan con #

In [2]:
# Esto es un comentario

El final de línea indica el fin de una sentencia (no es necesario marcarlo con ; ni con ningún otro carácter)

In [3]:
x = 5
y = 10

La indentación (indentation) es clave.

En Python los bloques de código se indican por indentación. El uso de indentación ayuda a reforzar el estilo uniforme y legible que muchos encuentran atractivo en el código Python.

In [4]:
lower = []; upper = []
for i in range(10):
    if i < x:
        lower.append(i)
    else:
        upper.append(i)
        
print('lower: ', lower)
print('upper: ', upper)
lower:  [0, 1, 2, 3, 4]
upper:  [5, 6, 7, 8, 9]

Una puntualización sobre print:

In [5]:
# Python 2 only!
# print "first value:", 1
# Python 3 only!
print("first value:", 1)
first value: 1

Variables y Objetos

Variables

En Python las variables son punteros.

In [6]:
x = 4

x es un puntero que apunta a un espacio de memoria donde se almacena el valor 4.

Una consecuencia de esto es que no tenemos que declarar el tipo de x, es un puntero que puede apuntar a información/datos de otro tipo. Por eso se dice que Python es un lenguaje de tipado dinámico y podemos hacer cosas como:

In [7]:
x = 1         # x is an integer
x = 'hello'   # now x is a string
x = [1, 2, 3] # now x is a list

Esta es otra de las propiedades de Python que permite que sea rápido de escribir y fácil de leer.

¡Cuidado!

Este tipado dinámico tiene otras consecuencias que hay que tener en cuenta.

In [8]:
x = [1, 2, 3]
y = x
In [9]:
print('x: ', x)
print('y: ', y)
x:  [1, 2, 3]
y:  [1, 2, 3]
In [10]:
x.append(4)
In [11]:
print(y)
[1, 2, 3, 4]

Hemos usado el puntero x para cambiar el contenido del espacio de memoria al que apunta, por eso el valor de y cambia (y es otro puntero que apunta al mismo espacio de memoria que x).

Sin embargo, si usamos '=' para modificar x:

In [12]:
x = 'hasta luego'

print(y)
[1, 2, 3, 4]

Al usar '=' hemos hecho que x apunte a un espacio distinto.

El contenido del espacio de memoria al que apuntaba antes x (al que sigue apuntando y) está intacto.

In [13]:
x = 10
y = x
x += 5  # add 5 to x's value, and assign it to x
print("x =", x)
print("y =", y)
x = 15
y = 10

Objetos

Python es un lenguaje de programación orientada a objetos, y en Python todo es un objeto.

Acabamos de ver que en Python las variables son simplemente punteros y que la declaración del nombre de una variable no lleva asociada información sobre su tipo. Sin embargo, eso no significa que Python sea un lenguaje sin tipos.

Python tiene tipos; pero no van asociados al nombre de las variables sino a los objetos en sí.

In [14]:
x = 4
type(x)
Out[14]:
int
In [15]:
x = 'hello'
type(x)
Out[15]:
str
In [16]:
x = 3.14159
type(x)
Out[16]:
float

Operadores de identidad y pertenencia

Operadores de identidad: 'is' e 'is not'

Identidad o identidad de objeto es distinto a igualdad.

In [17]:
a = [1, 2, 3]
b = [1, 2, 3]
In [18]:
a==b
Out[18]:
True
In [19]:
a is b
Out[19]:
False
In [20]:
a is not b
Out[20]:
True

a y b apuntan a distintos objetos.

Antes hemos visto que en Python las variables son punteros. El operador 'is' comprueba si las dos variables están apuntando al mismo contenedor/espacio de memoria (al mismo objeto). Así, la mayoría de las veces que un principiante tiene la tentación de usar 'is' lo que de verdad quiere hacer es '=='.

In [21]:
a=b
In [22]:
a is b
Out[22]:
True

Operadores de pertenencia

Estos operadores comprueban pertenencia en objetos compuestos.

In [23]:
1 in [1,2,3]
Out[23]:
True
In [24]:
2 not in [1,2,3]
Out[24]:
False

En los lenguajes de programación orientados a objetos como Python, un objeto es una entidad que contiene datos junto con metadatos y/o funcionalidad asociados.

En Python todo es un objeto, lo que significa que cada entidad tiene algunos metadatos (llamados atributos) y funcionalidad asociada (llamados métodos). Se accede a estos atributos y métodos a través de la sintaxis de puntos.

In [25]:
L = [1, 2, 3]
L.append(100)
print(L)
[1, 2, 3, 100]

Quizá nos resulte más extraño que no solo objetos compuestos (como las listas) sino también tipos simples de objetos tengan atributos y métodos asociados.

In [26]:
x = 4.5
print(x.real, "+", x.imag, 'i')
4.5 + 0.0 i
In [27]:
x.is_integer()
Out[27]:
False
In [28]:
x = 4.0
x.is_integer()
Out[28]:
True

Incluso los atributos y métodos de objetos son a su vez objetos con su propia información de tipo.

In [29]:
type(x.is_integer)
Out[29]:
builtin_function_or_method

Flujo de control

Condicionales

In [30]:
x = -15

if x == 0:
    print(x, "es cero")
elif x > 0:
    print(x, "es positivo")
elif x < 0:
    print(x, "es negativo")
else:
    print(x, "no se parece a nada que haya visto antes ...")
-15 es negativo

Tanto los dos puntos (:) como los espacios en blanco (indentación) se utilizan para indicar bloques de código separados.

Bucles

Bucle for

Vamos a ver algunos ejemplos de bucles for en Python.

Si queremos imprimir cada uno de los elementos de una lista podríamos usar:

In [31]:
for N in [2, 3, 5, 7]:
    print(N, end=' ') # print all on same line
2 3 5 7 

Especificamos la variable que queremos usar, la secuencia sobre la que queremos iterar, y usamos el operador 'in' para unirlos de forma intuitiva y legible. El objeto a la derecha del operador 'in' puede ser cualquier iterador de Python.

Uno de los iteradores más usados en Python es el objeto range que genera una secuencia de números:

In [32]:
for i in range(10):
    print(i, end=' ')
0 1 2 3 4 5 6 7 8 9 
In [33]:
# range from 5 to 10
list(range(5, 10))
Out[33]:
[5, 6, 7, 8, 9]
In [34]:
# range from 0 to 10 by 2
list(range(0, 10, 2))
Out[34]:
[0, 2, 4, 6, 8]

Bucle while

In [35]:
i = 0
while i < 10:
    print(i, end=' ')
    i += 1
0 1 2 3 4 5 6 7 8 9 

break and continue

Dos sentencias útiles que podemos utilizar dentro de los bucles para ajustar con precisión cómo se ejecutan.

  • break: rompe completamente el bucle
  • continue: salta lo que quede de la iteración actual del bucle y pasa directamente a la siguiente iteración

Ambos pueden utilizarse tanto en bucles for como en while

In [36]:
for n in range(20):
    # si el resto de n / 2 es 0, sáltate el resto del bucle
    if n % 2 == 0:
        continue
    print(n, end=' ')
1 3 5 7 9 11 13 15 17 19 
In [37]:
# Completa una lista con la sucesión de Fibonacci hasta un valor determinado
a, b = 0, 1
amax = 100
L = []

while True:
    (a, b) = (b, a + b)
    if a > amax:
        break
    L.append(a)

print(L)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Funciones

En Python las funciones se definen con la sentencia def.

In [38]:
def fibonacci(N):
    L = []
    a, b = 0, 1
    while len(L) < N:
        a, b = b, a + b
        L.append(a)
    return L

Hemos definido una función llamada fibonacci que tiene un único argumento N y que devuelve un valor, una lista con los N primeros números de la sucesión de Fibonacci.

In [39]:
fibonacci(10)
Out[39]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

No se especifica ninguna información de tipos sobre la entrada o salida de la función. Una función puede devolver cualquier objeto de Python simple o compuesto.

A veces cuando definimos una función, hay ciertos valores que queremos que la función use la mayor parte del tiempo (o que creemos que son los que se van a usar mayoritariamente) pero también nos gustaría dar al usuario opciones/flexibilidad. En esos casos, podemos usar valores por defecto para los argumentos de la función.

In [40]:
def fibonacci(N, a=0, b=1):
    L = []
    while len(L) < N:
        a, b = b, a + b
        L.append(a)
    return L
In [41]:
fibonacci(10)
Out[41]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
In [42]:
fibonacci(10, 0, 1)
Out[42]:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
In [43]:
fibonacci(10, 0, 2)
Out[43]:
[2, 2, 4, 6, 10, 16, 26, 42, 68, 110]
In [44]:
fibonacci(10, b=3, a=1)
Out[44]:
[3, 4, 7, 11, 18, 29, 47, 76, 123, 199]

Módulos y paquetes

Para poder cargar y utilizar módulos ya existentes utilizamos la sentencia import.

Podemos importar un módulo directamente. Cuando hagamos referencia al contenido de dicho módulo tendremos que indicar el namespace:

In [45]:
import math
math.cos(math.pi)
Out[45]:
-1.0

Podemos importar un módulo asignándole un alias:

In [46]:
import numpy as np
np.cos(np.pi)
Out[46]:
-1.0

Si solo nos interesa importar algunos elementos concretos del módulo también podemos hacerlo:

In [47]:
from math import cos, pi
cos(pi)
Out[47]:
-1.0

También podemos importar todo el contenido de un paquete en el espacio de nombres (namespace) local

In [48]:
from math import *
sin(pi) ** 2 + cos(pi) ** 2
Out[48]:
1.0

Esta opción debe usarse con cuidado y moderación, si es que se usa. El problema es que tales importaciones pueden a veces sobrescribir nombres de funciones que no se pretende sobrescribir, y puede ser difícil darse cuenta de lo que hemos cambiado. Por ejemplo:

In [49]:
help(sum)
Help on built-in function sum in module builtins:

sum(iterable, start=0, /)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers
    
    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

In [50]:
sum(range(5), -1)
Out[50]:
9
In [51]:
from numpy import *
sum(range(5), -1)
Out[51]:
10

En el primero, sumamos range(5) empezando en -1; en el segundo, sumamos range(5) a lo largo del último eje (indicado por -1).

Módulos de "terceros"

Una de las cosas que hace que Python sea útil, especialmente en el mundo de la ciencia de datos, es su ecosistema de módulos de terceros. Estos pueden ser importados como los módulos built-in, pero primero tenemos que instalarlos en nuestro sistema. El registro estándar para estos módulos es el Python Package Index (PyPI), que se encuentra en la Web en http://pypi.python.org/. Cuando queramos instalar uno de estos módulos en nuestro sistema usaremos el programa pip. Por ejemplo, si quisiéramos instalar el módulo supersmoother:

$ pip install supersmoother

El código fuente del paquete se descargará automáticamente del repositorio de PyPI, y el paquete se instalará en la ruta estándar de Python (suponiendo que tenemos los permisos).