Está en la página 1de 4

Python: Inyeccin de Dependencias DESARROLLO

Tecnologas estrella de los ltimos aos ahora en Python

INYECCIN DE DEPENDENCIAS
Spring, la gran revolucin en el mundo Java, siempre fue algo extrao y alejado para la plataforma Python, pero han ido surgiendo alternativas que nos permiten aprovecharnos de sus ventajas en nuestros programas. POR JOS MARA RUZ
a Inyeccin de Dependencias fue la tecnologa que revolucion el software empresarial durante la primera dcada del siglo XXI. Spring arremeti en el mundo Java con una fuerza enorme, llegando a destronar a J2EE como framework por excelencia para el desarrollo de grandes aplicaciones y permitiendo al mundo Java crear software de forma ms gil y mantenible [1]. Pero, qu es todo eso de la Inyeccin de Dependencias? Y lo ms importante, por qu debera importarnos a los que usamos Python? Para responder a estas dos preguntas vamos a echar un vistazo a tres libreras que nos permiten usarla en Python: SpringPython SnakeGuice Python-Injector

>>> cobro = U EntidadFinanciera()

En cada sitio en el que necesitemos realizar un cobro deberemos obtener una instancia de EntidadFinanciera(). Lo ideal sera poder cambiar en un solo sitio que instancia recogeremos. O sea, necesitamos algn sistema que nos permita evitar tener que usar constructores y an as obtener la instancia que queremos. A esto se le denomina inyeccin de dependencias. Consiste en inyectar las instancias de las clases.

Spring Python
Spring es un framework cuya misin es gestionar la inyeccin de dependencias en proyectos software Java, existiendo desde no hace demasiado una versin del mismo para Python. Podemos instalar SpringPython a travs de PyPi [2] mediante pip o easy_install. Todo software de Inyeccin de Dependencias necesita emplear algn sistema de configuracin que nos permita relacionar qu instancias sern inyectadas para unas determinadas clases. Existen varias formas de hacerlo en SpringPython. Podemos usar ficheros en XML, YAML o Python. Como no queremos aprender un nuevo lenguaje, vamos a emplear el tercer mtodo, que nos permitir reutilizar nuestro conocimiento de Python y no nos obligar a emplear multitud de ficheros XML (una de las partes ms problemticas de Spring en Java hasta hace poco). Vamos a emplear el cdigo que aparece en el Listado 1 y en el Listado 2. El primero de ellos nos muestra un fichero de configuracin de Spring mediante Python. Definimos las clases Coche y CocheElectrico que responden al

La Inyeccin de Dependencias
Cuando creamos un nuevo objeto de alguna clase, debemos invocar al constructor del mismo. Esto es un problema en muchas ocasiones y especialmente cuando nuestro software es complejo. El empleo de constructores para la generacin de instancias es un problema porque se rompe la norma Dont Repeat Yourself (No Te Repitas), que dice que en nuestro software solo debera existir un punto donde sea preciso cambiar el cdigo para poder cambiar el comportamiento del sistema. Pensemos que queremos probar nuestro programa y que una determinada clase (digamos una que hace un cobro) debe ser reemplazada por otra que en realidad no lo haga, no queremos hacer cientos de cobros falsos a nuestros clientes. Cmo podemos hacer este cambio sin tocar el cdigo que vamos a probar?

mismo mtodo y por tanto tienen el mismo interfaz. A continuacin definimos la clase VehiculoAppContext que hereda de PythonConfig. Dentro de esta clase podemos definir mtodos que representarn interfaces. En nuestro caso definimos un mtodo llamado Coche que devolver una instancia que debera respetar comportarse como un coche. Para que Spring considere a este mtodo como una interfaz hay que emplear el decorador Object, que acepta como parmetro el comportamiento que queremos que tenga nuestro objeto. En este caso queremos que cada vez que se llame a Coche se genere una nueva instancia, por lo que lo configuramos con scope.PROTOTYPE. Los mtodos que no estn decorados con Object no tendrn significado especial para Spring, que es el caso de MetodoNormal. Ya tenemos nuestra configuracin. Ahora vamos a ponerla en uso con un programa muy sencillo que se puede ver en el Listado 2. Comenzamos creando un objetoApplicationContext al que le pasamos nuestra configuracin generando una instancia de VehiculoAppContext y al que podramos tambin haber pasado un fichero de configuracin en XML o en YAML. A la variable que contiene la instancia de ApplicationContext se le suele dar el nombre de ctx, como abreviatura de context. ctx ser nuestro interfaz con Spring y nos permitir obtener instancias del interfaz Coche mediante el mtodo get_object(). Si ahora queremos cambiar la instancia que devolver get_object(), debemos cambiar el fichero config.py, cambiando el comportamiento de nuestro programa. Esto nos permite cumplir el principio DRY (Dont Repeat Yourself), lo que ayudar a mantener y a comprender nuestro programa.

ioannis kounadeas - Fotolia.com

WWW.LINUX- MAGAZINE.ES

Nmero 64

45

DESARROLLO Python: Inyeccin de Dependencias

Aspect Oriented Programming


Spring ayud a popularizar el uso del AOP (Aspect Oriented Programming, [3]. El AOP es un conjunto de tcnicas que buscan aadir comportamiento a nuestro cdigo fuente sin ensuciarlo (el objetivo de la Inyeccin de Dependencias). Para ello es necesario poder aadir cdigo que sea llamado en determinados puntos de la ejecucin de nuestro programa de forma externa a ste. Digamos que queremos emitir un mensaje de log cuando se llame a cierta funcin en nuestro programa, el AOP debe permitirnos definir el cdigo que se ejecutar cuando nuestra funcin sea invocada sin alterar nuestro programa. El Listado 3 muestra el empleo ms simple de AOP que nos permite SpringPython. Defi-

Listado 1: Configurando la Inyeccin en Spring


01 from springpython.config import PythonConfig 02 from springpython.config import Object 03 from springpython.context import scope 04 05 class Coche(object): 06 def arranca(self): 07 return bruuuuuum bruuuuum 08 09 def __str__(self): 10 return Soy un coche de gasolina 11 12 class CocheElectrico(object): 13 def arranca(self): 14 return ziiiiiim 15 16 def __str__(self): 17 return Soy un coche elctrico 18 19 class VehiculoAppContext (PythonConfig): 20 def __init__(self): 21 super(VehiculoAppContext, self).__init__() 22 23 @Object(scope.PROTOTYPE) 24 def Coche(self): 25 self.logger.debug(Creando un coche) 26 return CocheElectrico() 27 28 def MetodoNormal(self): 29 pass

nimos una clase Coche con los mtodos acelera() y cantidadCombustible(). Queremos dar una seal de alerta cuando el combustible baje del 40%. Creamos la clase Interceptor que controlar la llamada a un mtodo, controlando si el primer argumento que recibe es mejor que 40, en cuyo caso emitir un mensaje de alerta. Para ello hace uso del parmetro invocacion, que contiene los datos del mtodo interceptado. Una vez hemos hecho nuestra comprobacin llamamos al mtodo invocacion.proceed(), que ejecutar el mtodo interceptado de forma normal. Cmo conectamos nuestro interceptor con la clase a interceptar? SpringPython nos ofrece un sistema basado en proxies, objectos que envuelven a otros objetos para controlarlos. En nuestro caso hacemos uso de ProxyFactoryObject, que acepta como parmetros el objeto a controlar y los interceptores que se dispararn. Tambin podramos haber hecho uso de ProxyFactory, que realiza la

misma tarea pero sobre una clase en lugar de un objeto. Para determinar el mtodo a interceptar, porque una clase puede tener muchos, emplearemos RegexpMethodPointcutAdvisor, el cual nos permite usar una expresin regular para localizar el mtodo, o mtodos, a interceptar. El nombre de los mtodos comienza con el nombre de la clase, por lo que nuestra expresin regular debe contener un .* delante para que funcione. Por ltimo llamamos a los mtodos acelera() y cantidadCombustible(), y aparecer lo siguiente en pantalla:
Acelerando a 100 km/h Alerta! queda poco combustible. El depsito tiene 30

Listado 2: Obteniendo Instancias con Spring


01 from config import VehiculoAppContext 02 from springpython.context import ApplicationContext 03 04 ctx = ApplicationContext (VehiculoAppContext()) 05 vehiculo = ctx.get_object(Coche) 06 07 print vehiculo.arranca()

SpringPython dispone de una gran cantidad de libreras que son un fiel reflejo de la versin Java del framework. Adems, se ha publicado recientemente un libro sobre SpringPython que complementa la documentacin existente. SpringPython est respaldado por SpringSource, la empresa que explota comercialmente Spring, por lo que ser visto con buenos ojos en aquellas empresas que ya han estado trabajando con Spring. Su mayor defecto, en mi opinin, es que se aleja bastante de la filosofa Python: el cdigo es complejo y se recurre a conceptos ajenos al mundo Python y ms cercanos al de Java. La documentacin existente en su web deja bastante que desear, haciendo continuas referencias a la documentacin de Spring Java. Si has usado Spring, SpringPython te permitir reutilizar tus conocimientos.

Listado 3: AOP en Funcionamiento en Spring


01 # -*- coding: utf-8 -*02 03 from springpython.aop import * 04 05 class Interceptor(MethodInterceptor): 06 def invoke(self, invocacion): 07 if invocacion.args[0] < 40: 08 print Alerta! queda poco combustible. 09 invocacion.proceed() 10 11 class Coche: 12 def acelera(self, velocidad): 13 print Acelerando a {0} km/h.format(velocidad) 14 15 def cantidadCombustible(self, cantidad): 16 print El depsito tiene {0}.format(cantidad) 17 18 pointcutAdvisor = RegexpMethodPointcutAdvisor( advice = [Interceptor()], 19 patterns = [.*cantidadCombustible$]) 20 21 coche = ProxyFactoryObject(target = Coche(), interceptors = pointcutAdvisor) 22 23 coche.acelera(100) 24 coche.cantidadCombustible(30)

46

Nmero 64

WWW.LINUX- MAGAZINE.ES

Python: Inyeccin de Dependencias DESARROLLO

SnakeGuice
Google reconoci hace aos la necesidad de emplear la Inyeccin de Dependencias, pero cre su propio framework: Guice [4]. Guice se dise para que el desarrollo de software en Java no implicase decenas de ficheros XML de configuracin (algo normal en Spring) y para que fuese muy eficiente. Los creadores de Guice optaron por crear un sistema donde la configuracin se haca mediante el propio lenguaje de programacin, lo que permita validarla y simplificaba la vida de los desarrolladores. El proyecto SnakeGuice trae la esencia de Guice a Python mediante una implementacin bastante reducida respecto a la que ofrece la versin Java de Guice. Para hacer uso de l hay que descargarlo desde la web del Recurso 4, puesto que no se encuentra en PyPi en el momento de escribir este artculo. Guice hace uso de decoradores as como de objetos para realizar la configuracin. SnakeGuice realiza la Inyeccin de Dependencias empleando el mtodo tradicional: a travs del constructor de las clases, como podemos ver en el Listado 4. Para ello, Guice emplea un sis-

tema que podemos llamar de tres capas. En la primera capa tenemos el objeto en el que queremos realizar la inyeccin de la dependencia. En nuestro caso, ese objeto es una clase llamada Coche que admite como unos de sus parmetros el motor que tendr el vehculo. El decorador Inject nos permite indicar a SnakeGuice que vamos a realizar una inyeccin, y le pasamos como parmetro el nombre del argumento (o argumentos) del constructor que queremos inyectar y la clase de la que queremos un objeto. Esta clase es el segundo nivel, puesto que representa una interfaz que debe ser respetada. La costumbre es anteponer una I al nombre de la interfaz, y as no confundiremos las clases que representan interfaces con las que las implementan. Ahora entramos en la tercera capa. Guice nos permite definir qu clase implementa una determinada interfaz. Para ello debemos crear una clase que en el mundo de Guice se suele llamar mdulo. Dicho mdulo debe tener un mtodo configure que admite un parmetro llamado binder. ste nos permite conectar interfaz e implementacin, como podemos observar en la clase CocheModule en el Lis-

tado 4. Todos los enlaces se realizarn en CocheModule, por lo que cumpliremos con el principio Dont Repeat Yourself. Guice tambin nos permite ser ms selectivos en cuanto a qu clase implementa una determinada interfaz gracias a las anotaciones. Una anotacin no es otra cosa que una cadena de texto que selecciona la implementacin que usaremos. Si en CocheModule hubisemos creado los siguientes enlaces:
binder.bind(IMotor, U annotated_with=electrico,U to=MotorElectrico) binder.bind(IMotor, U annotated_with=gasolina,U to=MotorGasolina)

y en Coche hubisemos empleado:


@inject(motor=IMotor) @annotate(motor=gasolina)

Listado 4: Ejemplo de Uso de SnakeGuice


01 from snakeguice import inject, Injector 02 03 class MotorGasolina(object): 04 def __str__(self): 05 return Motor a gasolina 06 07 class MotorElectrico(object): 08 def __str__(self): 09 return Motor elctrico 10 11 class IMotor(object): 12 pass 13 14 class CocheModule(object): 15 def configure(self, binder): 16 binder.bind(IMotor, to=MotorElectrico) 17 18 class Coche(object): 19 20 @inject(motor=IMotor) 21 def __init__(self, motor): 22 self.motor = motor 23 24 injector = Injector (CocheModule()) 25 coche = injector.get_instance (Coche) 26 print coche.motor

entonces se seleccionara MotorGasolina como implementacin. Esto nos permite ser ms selectivos y disear unos enlaces ms precisos. Nos queda por ver cmo funciona el sistema de inyeccin de SnakeGuice. Para poder hacer uso de las inyecciones, necesitamos hacer algo muy parecido a lo que vimos en Spring. En lugar de un contexto se emplea una instancia de la clase Injector que recibe como parmetro el mdulo de configuracin CocheModule, y ser a travs del mtodo get_instance() como obtendremos nuestros objectos. Como punto negativos de SnakeGuice, dir que no parece estar siendo mantenido, su documentacin es bastante escasa y no parece muy relevante en la comunidad. Adems, se queda bastante corto en sus prestaciones y caractersticas.

Python Inject
Listado 5: Python Inject, Inyeccin de Parmetro Esttico
01 from datetime import datetime 02 import inject 03 04 class Mensaje(object): 05 06 @inject.param(fecha, datetime.now) 07 def __init__(self, texto, fecha): 08 self.texto = texto 09 self.fecha=fecha 10 11 def __str__(self): 12 return u{0} => {1}.format(self.texto, self.fecha) 13 14 print Mensaje(Hola mundo) 15 print Mensaje(Hola mundo, datetime(2009,10,23))

El proyecto Python Inject [5] tambin apuesta por un sistema parecido a Google Guice. Al contrario que SnakeGuice, parece que lleva cierta andadura y que est siendo mantenido. En el momento de escribir este artculo se encuentra en su versin 1.0.1, y es posible instalarlo desde PyPi sin problemas. Al contrario que SnakeGuice, Inject implementa muchas ms caractersticas de la versin original de Guice. Comencemos por realizar una inyeccin sencilla que podemos ver en el Listado 5. Vamos a inyectar el valor de un parmetro del constructor de Mensaje mediante el decorador

WWW.LINUX- MAGAZINE.ES

Nmero 64

47

DESARROLLO Python: Inyeccin de Dependencias

@inject.param que acepta dos parmetros: el nombre del parmetro a inyectar y su valor. En este caso es una inyeccin algo tonta, puesto que Python nos permite especificar valores por defecto para los parmetros de las funciones. Pero si ejecutamos este cdigo, veremos que hemos hecho algo ms. El resultado de su ejecucin ser:
Hola mundo => 2010-09-01 04:55:19.776917 Traceback (most recent call last): File ./python-inject.py, line 25, in <module> print Mensaje(Hola mundo, datetime(2009,10,23)) File /usr/local/lib/ python2.6/site-packages/ inject/injections.py, line 118, in injection_wrapper return func(*args, **kwargs) TypeError: __init__() got multiple values for keyword argument fecha

nombre del parmetro. La clase Coche indica tanto el nombre del parmetro como la interfaz que se emplear (tan como vimos en SnakeGuice), mientras que la clase Camin adems emplea una anotacin para ser ms selectiva. Ms abajo vemos cmo creamos una instancia de inject.Injector, que ser responsable de gestionar las inyecciones, y posteriormente registramos nuestro inyector en el sistema de Python Inject. El resultado de ejecutar este programa ser:
Camin con motor de gasoleo. Coche con motor de gasolina. Motocicleta con motor elctrico.

@inject.appscope class BaseDeDatos(object): ...

Python Inject adems incorpora una librera para construir servidores web WSGI, por lo que puede emplearse para el desarrollo de aplicaciones web.

Conclusin
Aunque el mundo Python no presta demasiada atencin a la Inyeccin de Dependencias, sta es a da de hoy la base de alguna de las mayores aplicaciones informticas del mundo. Es impensable un gran desarrollo en Java o C# sin la Inyeccin de Dependencias. An as, el mundo Python se ha defendido muy bien sin esta tecnologa durante mucho tiempo. Las distintas alternativas que hemos podido ver en este artculo nos permiten hacer uso de ella si as fuera preciso. I

Lo que nos indica que las inyecciones se han realizado correctamente. Python Inject nos permite an ms juego. Podemos querer inyectar una dependencia en un campo de una clase en lugar de usar el constructor:
class Autobus(object): motor = inject.attr(motor, U IMotor, annotation=camion)

RECURSOS
[1] Qu es la Inyeccin de Dependencias?: http://es.wikipedia.org/wiki/ Inyecci%C3%B3n_de_dependencias [2] SpringPython: http://springpython. webfactional.com/ [3] Aspect Oriented Programming: http://es.wikipedia.org/wiki/ Programaci%C3%B3n_Orientada_a_ Aspectos [4] SnakeGuice: http://code.google.com/ p/snake-guice/ [5] Python-Inject: http://code.google. com/p/python-inject/

No nos permite pasarle el parmetro fecha al constructor! Nuestro constructor slo admite ahora el parmetro mensaje. En el Listado 6 podemos ver cmo funciona la inyeccin de clases. Python Inject nos ofrece muchas posibilidades en lo que se refiere a inyecciones, y en este ejemplo podemos ver tres mtodos diferentes. La clase Motocicleta admite un parmetro motor, y nosotros realizamos la inyeccin contra el parmetro. No indicamos qu interfaz seguiremos, simplemente especificamos el

Tambin podemos indicar que determinada clase es un singleton, una clase de la que slo puede existir un objeto para todo el programa. Esto es muy til en clases que acceden a recursos. Imaginemos que debemos acceder a una base de datos y que queremos asegurarnos de que slo emplearemos una conexin. En tal caso podemos realizar una inyeccin as:

Listado 6: Python Inject, Tres Formas de Inyeccin


01 import inject 02 03 class MotorGasoleo(object): 04 def __str__(self): 05 return motor de gasoleo. 06 07 class MotorGasolina(object): 08 def __str__(self): 09 return motor de gasolina. 10 11 class MotorElectrico(object): 12 def __str__(self): 13 return motor elctrico. 14 15 class IMotor(object): 16 pass 17 18 class Camion(object): 19 @inject.param(motor, IMotor, annotation=camion) 20 21 22 23 24 def __init__(self, motor): self.motor = motor def __str__(self): return Camin con {0}.format(self.motor) 38 39 def __str__(self): 40 return Motocicleta con {0}.format(self.motor) 41 42 inyector = inject.Injector() 43 inyector.bind(IMotor, annotation=camion, to=MotorGasoleo) 44 inyector.bind(IMotor, to=MotorGasolina) 45 inyector.bind(motor, to=MotorElectrico) 46 inject.register(inyector) 47 48 print Camion() 49 print Coche() 50 print Motocicleta()

25 26 class Coche(object): 27 @inject.param(motor, IMotor) 28 def __init__(self, motor): 29 self.motor = motor 30 31 def __str__(self): 32 return uCoche con {0}.format(self.motor) 33 34 class Motocicleta(object): 35 @inject.param(motor) 36 def __init__(self, motor): 37 self.motor = motor

48

Nmero 64

WWW.LINUX- MAGAZINE.ES