Introspección en Python

marzo 20, 2010

Lo primero que nos choca en el título es la palabra “Introspeccón”, no es un termino que estemos acostumbrados a utilizar en nuestro día a día, incluso nos cuesta deletrearlo. Segun la RAE, Instropección es “Observación interior de los propios actos o estados de ánimo o de conciencia.”. Esta explicación se acerca a su significado cuando nos referimos a los lenguajes de programación. Decimos que un lenguaje o plataforma tiene la capacidad de ser instrocpectivo cuando podemos, dinámicamente, mirar dentro del código que se está ejecutando, esto es, podemos consultar diversos aspectos del código en tiempo de ejecución.

Esta caracteristica es común en muchos de los lenguajes modernos como Java, C# y de la mayoría de lenguajes de script más utilizados como Ruby, Python o Javascript.

Utilizando reflexión podemos llevar a cabo tareas muy interesantes, como por ejemplo, construir herramientas de pruebas unitarias genéricas, o en el caso de desarrollar un IDE, estas capacidades son claves para poder brindar un sistema de sugerencia de código, como hace Eclipse o Netbeans.

Entrando en temas mas abstractos, gracias a la reflexión podemos diseñar un programa que sea adaptativo, esto es, que pueda crear métodos dinamicamente según las necesidades del cliente.

Usando Python, vamos a crear un script que llame a todos los métodos de todos los ficheros .py que se encuentren en el mismo directorio. Todos estos ficheros contendran funciones que admiten valores de entrada númericos.

Este programa puede ser útil de cara a crear entornos de pruebas unitarios.

Para ello, Python nos brinda varios mecanismos de instrospección.

El primero de todos es dir, esta función nos devuelve una lista de todos los elementos de un objeto, este objeto puede ser un módulo, un fichero con diversas funciones, o una clase de python.

Por ejemplo si tenemos un sencillo fichero llamado operaciones.py:

version = '1.0'

def suma(a, b):
	return a+b

def resta(a,b):
	return a-b

Si hacemos dir(operaciones) la lista de resultados, además de los elementos estandar como __name__, contendrá las funciones suma y resta y el atributo versión.

La siguiente función clave es getattr. Con esta función podremos obtener un elemento de un objeto, la ventaja es que podemos hacerlo en tiempo dinámico, por ejemplo si hacemos getattr(operaciones,’version’) se nos mostrará la cadena 1.0, esto sería equivalente a hacer operaciones.versión. Al igual que con los atributos podemos obtener también funciones e incluso llamarlas usando esta función. Por ejemplo lo equivalente a operaciones.suma(3,4) sería getattr(operaciones,’suma’)(3,4).

Ya casi tenemos todos los elementos que necesitamos para el proposito original, una de las cosas que aún necesitamos es saber si, teniendo un atributo, este es una función a la que podamos llamar. Para ello necesitamos la función callable. Esta función devolverá True cuando el elemento sea “llamable”. Por ejemplo, callable(getattr(operaciones, ‘suma’)) devolverá True.

Finalmente, lo único que nos falta por saber es el número de parámetros que la función tiene, para ello, usaremos la función getargspec, del módulo inspect. Esta fución nos devuelve una tupla con nombre, siendo uno de los elementos una lista con los parametros de la función. Así si llamamos inspect.getargsspec(operaciones.suma), el resultado será: ArgSpec(args=[‘a’, ‘b’], varargs=None, keywords=None, defaults=None).

Finalmente, el código de nuestro tester es el siguiente:

import os, inspect, random

for fileName in os.listdir('.'):
	if fileName.endswith('.py') & (fileName != 'tester.py'):
		moduleName = fileName[0:fileName.index('.py')]
		print '>> Trying to import ', fileName, ' moduleName: ', moduleName
		module = __import__(moduleName)
		for element in dir(module):
			moduleElement = getattr(module,element)
			if callable(moduleElement):
				params = []
				for param in inspect.getargspec(moduleElement).args:
					params.append(int(random.uniform(0,100)))
				print 'Calling ', moduleName,'->',element, '(', params, ')',
				try:
					print ' --- return Value: ', moduleElement(*params)
				except:
					print ' --- error!'

Por cada fichero del directorio donde se encuentre tester.py, analizará si es un fichero de Python, y si lo es tratará de importarlo y llamar a todas sus funciones. Como curiosidad, destaca el uso del operador de desempaquetado (*), y lo útil que es para llamar a una función con parametros, a priori, desconocidos.

En nuestro ejemplo, en el directorio teníamos aparte del fichero de operaciones.py, uno que contenía algunas operaciones matemáticas, llamado mathsThings, con dos funciones, getFiboNumber que nos devuelve el elemento n de la serie de Fibonacci, y isPrime que nos devuelve si un número es primo.
La salida al ejecutar tester.py es:


Generadores en Python

marzo 11, 2010

Si algo tiene de bueno aprender un lenguaje de script es que descubres nuevas maneras para hacer las cosas que un lenguaje tradicional como C o Java habitualmente no te ofrece.

En el caso de Python, existen múltiples cosas a las que los programadores no estamos acostumbrados, en este post concretamente vamos a hablar de los generadores.

Desde un punto de vista muy básico, un generador es un método para extraer los resultados de una función de una forma diferente a lo que tenemos acostumbrado a usar. Es un concepto mas cercano a la programacion funcional de lisp o haskell que a la programación procedimental de C o Pascal.

Normalmente lo usaremos cuando los datos que una funcion devuelve es una lista de datos. El enfoque tradicional sería construir la lista en la función y devolverla tal cual al llamador, pero esta forma de funcionar tiene alguna limitación.
Por ejemplo, si el proceso de recolectar la lista es muy costoso, el llamador no obtendrá ningún resultado hasta el final del proceso, y si para mas añadido, el llamador puede no necesitar todos los elementos en función de algún elemento de la lista; usando la forma típica de procesar este comportamiento, el llamador no tiene manera de obtener parcialmente esos resultados.

Además desde el punto de vista de la eficiencia, si la lista es muy grande, la estamos iterando dos veces, una al generarla y otra al procesarla.

Aparte de este uso, es frecuente encontrarse con funciones en que queremos ir extrayendo poco a poco resultados, el funcionamiento típico se basa en llamar repetidas veces a la función con el “estado” de dicha función pasado por parámetros desde el llamador, por ejemplo, en las típicas funciones que te buscan caracteres en una cadena de forma progresiva, cada vez que quieres un nuevo resultado, necesitas pasarle la cadena recortada hasta la anterior ocurrencia del caracter que buscamos. Con los generadores podremos hacer esto de una forma mucho mas natural.

Realmente esta forma de funcionar, no es que no se pueda hacer con C, por ejemplo, usando callbacks podemos simular el mismo comportamiento, pero sin duda la forma de afrontarlo en Python, es mucho mas sencilla, ya que entre otras cosas, está integrado como mecanismo genérico del lenguaje.

Como siempre la mejor forma de aprender algo es viendo un ejemplo sobre ello. Usando los generadores vamos a desarrollar una función que pasado un número binario lo irá “desgranando” y devolviendo los valores decimales por cada bit.

Esto es, si le pasamos a la funcion 10011, la salida será 1+2+0+0+16=19.

Si esto lo hicieramos de la forma habitual, la función tendría que devolver una lista con los elementos, lista que luego deberiamos iterar de nuevo para imprimir el resultado.

### EJEMPLO SIN USAR GENERADORES
def binToDecClassic(val):
	n = 0
	res = []
	while val > 0:
		res += [(val%2) * (2**n)]
		val /= 10
		n+=1

	return res

sum = 0
out = binToDecClassic(10011)
for val in out:
	print "+", val,
	sum += val

print "=", sum

Si usamos generadores, el cógido sería:

### EJEMPLO USANDO GENERADORES
def binToDecUsingGenerators(val):
	n = 0
	while val > 0:
		yield (val % 2) * (2**n)
		val = val / 10
		n += 1

sum = 0
for val in binToDecUsingGenerators(10011):
	print "+", val ,
	sum += val

print "=", sum

Como vemos, el código es muy similar, de hecho, la sintaxis puede llevar a confusión, ya que aunque vemos dos bucles, realmente sólo hay uno, en cada parada del for se corresponde con la instruccion “yield” dentro de la función. Esta palabra reservada, “yield” es la clave de los generadores. Cuando ponemos yield, devolvemos el control al llamador, con el resultado de la expresión que acompaña a dicha palabra, podemos decir que es como un return, pero que no finaliza definitivamente la función.

Este ejemplo, dada su sencillez para ver como funciona un generador, no aprovecha las ventajas que hemos comentado anteriormente, para ello, vamos a ver otro ejemplo algo más completo, para ello vamos a usar la funcion walk() del paquete estandar os. Esta función, dada un directorio raíz, va devolviendo poco a poco, usando “yield” los ficheros y subdirectorios de cada directorio. Por ejemplo, tenemos una función “searchInPath” que busca un fichero en un path:

import os

def searchInPath(path, filename):
    cont = 0
    for root, dirs, files in os.walk(path):
        if filename in files:
            return cont, True
        cont += 1
    return cont, False

[cont, found] = searchInPath('c:\\temp', 'prueba.py')
print cont,' steps - found: ', found

En cada directorio, se nos devolverá el control, de tal manera que si encontramos el fichero, no seguimos iterando por el árbol de directorios, si esto mismo lo haríamos de la forma tradicional, iterariamos por todo el árbol de directorios, devolveriamos la lista y luego deberíamos buscar en la lista de ficheros.


Aplicaciones gráficas con Processing y Arduino

marzo 2, 2010

En mis aventuras con Arduino, una de las cosas que he descubierto es la librería Processing.

En un principio no la conocía de nada por lo que el primer contanto con esta librería fue algo extraño. Al acceder a su página lo que te encuentras es una librería centrada en la creación de contenidos gráficos, y la pregunta es, ¿Qué tiene que ver esto con Arduino? Pues bastante, como vamos a ver 🙂

Si nos descargamos el entorno de Processing, lo primero que nos daremos cuenta es que el entorno de desarrollo de Arduino es completamente similar al de Processing. Más allá de una simple libería, Processing nos ofrece un completo entorno donde podamos desarrollar nuestros prototipos usando su propio lenguaje. Si bien, el lenguaje de processing es muy similar a Java.

Este concepto de entorno de desarrollo donde podemos crear el código y ejecutarlo es precisamente la misma idea de Arduino.

A la izquierda el editor de Arduino, a la derecha el de Processing

Uno de los beneficios de processing es que desde el punto de vista de la programación crear una animación es bastante sencillo, ya que la plataforma nos facilita la mayoría de los detalles. El objetivo es poder plasmar rapidamente nuestras ideas en un programa informático, cosa que no podemos conseguir de la misma forma usando los lenguajes y librerías tradicionales.

Sacado de la propia web de Processing, si queremos poner un par de lineas en pantalla, lo único que deberíamos escribir en nuestro “sketch” sería:

size(400, 400);
background(192, 64, 0);
stroke(255);
line(150, 25, 270, 350);
line(100, 25, 200, 300);

Desde el punto de vista de un desarrollador de Arduino, Processing, además de “prestarnos” su entorno de desarrollo, nos ofrece un medio muy interesante para añadir una parte visual a nuestro proyecto de Arduino. Como ya sabemos, Arduino es capaz de escribir y leer desde el puerto serie, por lo que usando esta interfaz, podemos recibir datos y enviar ordenes a nuestra placa.

Dado al nivel de unión que existe entre ambos proyectos, este “enlace” se realiza mediante la libreria Serial que existe en Processing. Usando esta librería, podemos leer del puerto serie, por lo que podremos adecuar nuestra animación según los datos que recibamos, así como enviarle datos al arduino según lo que se produzca en la aplicación gráfica.

Usando esta combinación se abre un buen abanico de posibilidades, ya que de una forma bastante sencilla podemos crear aplicaciónes gráficas bastante aparentes, que muestren datos recibidos del mundo externo.

Para terminar, os dejo con una animación hecha en Processing en youtube, y galería de proyectos en la web de Processing.