El enfoque minimalista de Python hace que sólo haya disponibles en el
espacio de nombres principal, es decir, sin prefijos, una serie de
funciones, menos de 100. Ya hemos visto otras funciones, timeit
y
random
que, aunque no estén cargadas por defecto, sí se pueden
usar directamente siempre que las importemos en nuestro espacio de
nombres.
Afortunadamente, el hecho de que Python sea software libre hace que se cree todo un ecosistema alrededor del mismo, con funciones que no están incluidos en el lenguaje, pero que añaden funcionalidad al mismo. Estos módulos generalmente están o en un repositorio, o referenciados en un directorio central de forma que se especifica desde dónde se descargan y qué otras dependencias tienen que cargarse antes de ser instalados.
En el caso de Python3, pip3
es la herramienta que se usa. Se instala
junto con el intérprete, y permite tanto buscar, como instalar, como
actualizar in situ
Vamos a usarla para trabajar con
bpython
es un intérprete alternativo de Python que permite trabajar
de forma más agradable, con una serie de goodies como
sintaxis coloreada, completado automático de órdenes y variables, y
también información interactiva. Vamos a instalarlo con
pip3 install bpython
Se ejecuta usando bpython
desde la línea de órdenes y el resultado
es algo así:
Entre otras cosas, este shell nos ayuda a insertar fácilmente los
métodos de un objeto determinado simplemente añadiendo un .
al final
del objeto: aparecen todos los métodos que tiene. En el
caso de la baraja
que hemos mostrado en la imagen anterior, nos
mostrará una serie de métodos que podemos seleccionar pulsando el
tabulador; cuando seleccionamos uno y añadimos un paréntesis,
bpython
nos explicará cuales son los parámetros que toma esta
función, lo que nos ahorra bastantes viajes a StackOverflow.
Vamos a usar a partir de ahora bpython
para el resto del capítulo, y
posiblemente del libro, así que conviene que esté convenientemente
instalado a partir de este momento. Adicionalmente, eso significará
que tienes permisos para instalarte cosas, sin el cual el resto del
capítulo tampoco va a tener mucho sentido.
Pero es posible que no tengas acceso a ningún ordenador de forma permanente. Estos tiempos bárbaros en los que la gente tiene acceso a cientos de ordenadores, pero ninguno de ellos tiene teclado o acceso de administrador, crean esas cosas. Así que nos tendremos que trasladar a
en una primera, y quizás mala, aproximación, la nube son recursos administrados por empresas que se pueden usar pagando sólo por el uso que se les da. Pero para lo que nos ocupa, son ordenadores que podemos usar desde cualquier navegador u ordenador y que en muchos casos se pueden usar de forma limitada gratis; por ejemplo, por un mes o siempre que el consumo de recursos no exceda una cantidad determinada.
Aunque las empresas que proveen la gama completa de soluciones en la nube es relativamente pequeña, una de las que nos puede proporcionar una cierta cantidad de recursos por valor de 50$ y por un mes es Azure de Microsoft. No necesita ningún tipo de tarjeta de crédito para darse de alta en la evaluación gratuita, pero si estás en un centro de enseñanza es posible que tengan créditos gratis.
También para experimentar puedes usar Cloud9. Aunque es en realidad un entorno de desarrollo colaborativo, permite acceder a la línea de órdenes desde el navegador, lo que nos permitirá trabajar con ella fácilmente, aunque sea por un tiempo limitado. Te puedes dar de alta, eso sí con una tarjeta de crédito, y usando tu ID de GitHub. Una vez dado de alta, se crea un workspace en Python con el nombre que uno quiera y automáticamente, con los ficheros y demás, aparece abajo una terminal desde la que se puede ejecutar el REPL de python y alguna cosa más. Se usa
sudo pip3 install bpython
por ejemplo, para instalar el CLI con el que vamos a trabajar.
El sistema de ficheros que se usa en Cloud9 es persistente, así que también puede ser útil para dejarlos de forma permanente; únicamente los ficheros con los que se trabaja son públicos. Puedes crear también espacios privados y compartirlos con otros usuarios, de forma que se puede trabajar colaborativamente sobre el mismo proyecto e incluso el mismo fichero. Usar Cloud9 sólo como un sitio con una CPU gratuita es no aprovechar todas sus posibilidades, pero también es cierto que introduciéndolo de esta forma se puede ir aprendiendo, poco a poco, todo lo que ofrece.
En sitios como Azure, Amazon o Google Compute Engine, sí puedes crear máquinas virtuales completas. En Azure se pueden buscar máquinas virtuales que tengan alguna característica determinada, como Python + Linux y aparecen unas cuantas, sobre todo enfocadas a ciencia de datos; también se pueden instalar directamente máquinas como Ubuntu Server o Red Hat Enterprise Linux; cualquiera de ellos incluye Python como herramientas por defecto.
Sin embargo, no es necesario ni siquiera instanciar una máquina virtual para trabajar con Python desde el navegador. Desde mayo de 2017, Azure incluye el Cloud Shell, un intérprete Linux en el navegador que, aunque sirve esencialmente para poder crear scripts de Azure, también tiene un usuario Linux con el que se puede trabajar. No hay, sin embargo, acceso a super usuario, por lo que tenemos dos opciones:
- Instalarlo como usuario, con lo que habrá que usar
pip3 install --user bpython
y posteriormente añadir
export PATH=$PATH:~/.local/bin
desde el shell para que se pueda usar directamente, o bien, como se ha
indicado en el primer capítulo, instalar pyenv
para tener la versión
de Python que se desee. bpython
en esta instalación irá así:
Este cloud shell se desactiva y se borra a los 10 minutos de inactividad. Si realmente quieres conservar lo creado, o la historia, es mejor que uses una máquina virtual real, que tendrás que recordar apagar al final de cada sesión, porque todo consume y el crédito gratuito que se obtiene no es mucho.
Ya no tienes excusa para empezar a trabajar con bpython
en la nube o
donde sea. Así que vamos a ver el
en el sentido de la palabra: cómo trabajar con diferentes estructuras
de datos siguiendo, dentro de lo posible, los preceptos de la
programación funcional. Dos
tipos de métodos son los más usados en este área: los iteradores y
los operadores sobre listas, incluyendo lo que se denomina list
comprehension. Los primeros se dejan para más adelante, pero los
segundos son tremendamente potentes y nos interesa usarlos aquí. Ya
hemos visto algunos, map
y filter
.
A estos podemos añadir zip
, que junto con sorted
nos puede ayudar
a comparar dos manos de cartas:
list(zip(sorted(['3♠','4♣','A♥']),sorted(['7♣', '7♥', '7♦'])))
[('3♠', '7♣'), ('4♣', '7♥'), ('A♥', '7♦')]
sorted
devuelve la lista ordenada, mientras que zip
crea una tupla
con un elemento de cada lista, devolviendo finalmente una lista de
tuplas, tal como la que se muestra. La programación funcional se
caracteriza por este tipo de cadenas: una función que recibe listas
y emite listas, de forma que se pueden encadenar y, en su caso,
dividir en diferentes hebras para hacerse de forma más eficiente. La
combinación de la programación funcional y la nube ha dado lugar a la
programación reactiva,
orientada a flujos de datos y que permite crear arquitecturas software
flexibles y escalables. Todo esto no nos preocupa ahora mismo, pero sí
aprender este estilo de programación, que puede aprovecharse en todo
tipo de lenguajes y situaciones. Esa orden, zip
, está presente en
todo tipo de lenguajes que incluyen características funcionales, como
Perl6, donde se haría:
sort ['3♠','4♣','A♥'] Z sort ['7♣', '7♥', '7♦']
((3♠ 7♣) (4♣ 7♥) (A♥ 7♦))
Perl6 se ahorra paréntesis cuando no hacen falta y convierte zip
en
el operador infijo Z
, es decir operador que, tal como los operadores
matemáticos, está en medio de los operandos, en vez de al principio
(lo que se suele denominar, en el caso de los operadores, prefijo).
Ejercicio: crear una serie de fracciones, desde 1/3 hasta 1/99 y hallar su suma.
Otra función, que estaba por alguna razón en Python 2 pero que ha sido
exiliada a functools
en Python 3 es reduce
. Reduce aplica
repetidamente una función a los elementos de una lista o tupla, a
partir de un valor inicial. Es decir, aplica al primer elemento y al
valor inicial; el resultado lo aplica junto con el segundo elemento y
así sucesivamente. Es una función recursiva, tal como la hemos visto
antes, pero la podemos usar fácilmente de esta forma:
from functools import reduce
def fact(n): return reduce( lambda prev,this: prev*this, range(1,n+1),1)
La función factorial, definida de esta forma, funciona exactamente
igual que la que hemos definido anteriormente de forma recursiva; la
recursión, por así decirlo, va por dentro. La función lambda
que
tenemos que pasar a reduce
tiene dos argumentos: el valor anterior
prev
y el valor actual de lo que le hemos pasado, this
. Vamos
multiplicando los valores a partir del 1, que en este caso no es
necesario, porque es el valor por omisión. range(1,n+1)
generará un
array [1,..., n+1]
y todo junto una función factorial que multiplica
un número por todos los que le preceden tal como estamos
acostumbrados.
Ejercicio: Programar una función de Mandelbrot, usando números complejos, de la misma forma.
No hace falta que el resultado final de un reduce
sea un sólo
número. Podemos por ejemplo ir creando una sucesión de Fibonacci,
partiendo de un valor inicial: los dos primeros valores.
def fib(n): return reduce( lambda prev,this: prev+ [prev[-2]+prev[-1]], rang
e(1,n+1), [1,1])
fib(12)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
Ejercicio: una generalización de las sucesiones de Fibonacci son las sucesiones de Lucas, que usan números arbitrarios como los números iniciales y que también multiplican los términos anteriores por números naturales. Programar la función
lucas
que genere estas sucesiones, y definir la función de Fibonacci como un caso particular de la misma.
Esta misma función, reduce
, está también presente en muchos otros
lenguajes de programación. En JavaScript, por ejemplo, es uno de los
métodos de los arrays:
[3,2,1].reduce( function(prev,este){ return este*prev; } )
y, como se ve, se usa la misma palabra clave, function
, para definir
lambdas
, lo que en realidad se llaman closures
o funciones
anónimas, que para definir funciones que no lo son: simplemente se
añade el nombre de la función previamente al paréntesis. Estos
ejemplos de list comprehension son ubicuos en los lenguajes de
programación, y Python tiene además una forma que combina map
y
filter
: la versión posfijo de for
.
[Fraction(1,n) for n in range(1,100) if n % 2]
Esta orden genera fracciones que tienen en el denominador todos los
números impares hasta el 100. Los corchetes que hay alrededor son los
que indican que se va a tratar de una lista; el primer término indica
la función que se va a aplicar a todos los elementos de la lista,
lista que se genera precisamente con la expresión for n in range(1,100)
; el if
al final filtra sólo los elementos que vamos a
usar. Fraction
es una clase para trabajar con quebrados; el primer
elemento es el numerador, el segundo es el numerador, aunque también
se le pueden pasar valores de esta forma: Fraction(1.3)
, que se
convertirá automáticamente en 13/10. Con esta serie de fracciones se
puede empezar a trabajar: hallar la suma, por ejemplo, o crear
las secuencias de Farey, que son
secuencias de listas de fracciones. Por
ejemplo,
se puede generar una aproximación al número e.
Incluso de esta forma se pueden generar listas más complejas, como la multiplicación de dos vectores:
[str(valor)+card for card in ["♠","♣","♥","♦"]
for valor in ['A','J','Q','K',2,3,4,5,6,7,8,9,10]]
El primer for
recorre los palos, el segundo los valores, y los dos
juntos se unen, usando +
para cadenas, en las cartas de la
baraja. Usamos str
alrededor de valor
simplemente para ahorrarnos
poner comillas alrededor de los números.
Ejercicio: para una urbanización con 4 bloques, 3 plantas por bloque, dos pisos por planta, A y B, generar todos los posibles pisos que hay en la forma que se considere más conveniente.
También se puede usar para almacenar valores en un array
o
diccionario. Por ejemplo, ¿son los valores aleatorios verdaderamente
aleatorios? Una forma es comprobar si el último bit se reparte de
forma equitativa entre 0 y 1, y los números (enteros) resultantes son
con igual probabilidad pares o impares.
from random import random
aleatorios = [int(random()*10000) for _ in range(10000)]
def cuenta_pares( prev, this ): prev[this&1 ] += 1; return prev;
from functools import reduce
reduce( cuenta_pares, aleatorios, [0,0])
Las dos sentencias from
importan dos funciones que necesitamos:
random
y reduce
. aleatorios
genera números aleatorios entre 0 y
10000, usando int
ya que random
devuelve valores entre 0
y 1. cuenta_pares
es la función que vamos a usar para reducir el
array a un solo objeto. El primer argumento sería una lista. Usa el
segundo argumento como un número, cuya paridad comprueba a base de
hacer un Y
por bits del último bit. Como el resultado es 0 o 1, usa
+=
para aumentar en 1 el valor de ese elemento de la lista.
+=
y en general cualquier operador más =
, es una asignación que
incluye el mismo elemento al que se asigna, es decir loquesea = loquesea OP otracosa
se escribiría loquesea OP= otracosa
; este
tipo de asignación + operador se suele encontrar en prácticamente
todos los lenguajes de programación; por ejemplo, en Ruby:
$a = [1,2,3]⤶
$a += [4]⤶
[1, 2, 3, 4]
En Ruby, las variables usan sigilos, en este caso el $
, para
designarse. Sin embargo, muchos lenguajes usan también algunos
operadores posfijo, detrás de la variable. En Perl, por ejemplo
$matricula = 'AA'⤶
$matricula++⤶
print $matricula⤶
AB
Este operador postincremento actúa, dependiendo del tipo que tenga,
numérico o de caracteres, efectivamente incrementando el último
carácter y convirtiendo la A en B. Sin embargo, en
Python
hay solo una forma obvia de hacer las cosas y
coincide con otros lenguajes, como Lua, evitando estos operadores de
postincremento e incluso otros como el preincremento que venían
usándose desde la época del C. Si ya hay una forma de hacerlo, usando
+=1
, ¿por qué complicarse buscando otra?
Ejercicio: Si habéis ejecutado el generador de números aleatorios de arriba, habréis visto que rara vez el resultado es exactamente la mitad de cada uno. ¿Cuanta desviación hay? Realizar una función que contabilice la desviación del generador de números aleatorios.
Trabajar de forma funcional te permite acercarte más a los diferentes
pasos en la ejecución de un problema, sin tener que pensar en estado,
sino directamente como funciones que se aplican a las estructuras de
datos que se han elegido. Python no es exactamente un lenguaje
funcional, pero los lenguajes modernos no pueden evitar tener ciertos
rasgos funcionales, aunque no estén en las funciones base. Alguna
función en un módulo como functools
completa bastante bien el
panorama.