Introspección en Python

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:

2 respuestas a Introspección en Python

  1. pul dice:

    Muy buen artículo

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: