Skip to content

Latest commit

 

History

History
370 lines (300 loc) · 15.9 KB

06.sinpilas.md

File metadata and controls

370 lines (300 loc) · 15.9 KB

Pilas no incluidas

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

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í:

usando bpython.

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

la nube.

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.

usando bpython en Cloud9.

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í:

bpython en Azure

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

Python más funcional,

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♦']
((37♣) (47♥) (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.

Concluyendo

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.