Últimos Cambios |
||
Blog personal: El hilo del laberinto |
Última Actualización: 03 de abril de 2007 - Martes
Python es un lenguaje de programación orientado a objetos, moderno y "hermoso". Como cualquier otro lenguaje orientado a objetos que se precie, incorpora el concepto de herencia. Mediante herencia podemos modificar el comportamiento de un objeto fácilmente.
El problema surge cuando los objetos que nos interesan son instanciados por una librería intermedia. Lo evidente es modificar la librería, pero eso rompe el encapsulamiento. Una posibilidad interesante sería que la librería estándar de python utilizase constructores opcionales, pero no es el caso.
¿Qué nos queda?.
Python permite modificar los objetos de forma dinámica. Es posible, por ejemplo, modificar los objetos de un módulo, de forma que cuando sean instanciados por la librería (que no hemos alterado), se creen instancias alteradas.
Veamos el siguiente código, que llamo "socket_decorator.py":
# $Id: socket_decorator.py 120 2007-04-03 01:10:03Z jcea $ from __future__ import with_statement import sys,socket,threading,time socket.socket_old=socket.socket lock_conexiones=threading.Lock() locks_conexiones={} LOCK_LIMPIEZA=3600 # Segundos lock_limpieza=time.time()+LOCK_LIMPIEZA class socket_new(socket.socket_old) : def connect(self,*args,**kwargs) : global lock_conexiones,locks_conexiones,lock_limpieza,time correo=False if args[0][1]==25 : correo=True with lock_conexiones : if time.time()>lock_limpieza : a=filter(lambda x: not x[1].locked(),locks_conexiones.items()) for i,j in a : del locks_conexiones[i] lock_limpieza=time.time()+LOCK_LIMPIEZA l=locks_conexiones.get(args[0][0],None) if l==None : l=locks_conexiones[args[0][0]]=threading.Lock() l.acquire() self.lock_ip=l self.direccion=args[0][0] try : self.bind(("IP_DE_SALIDA",0)) # Salimos con una IP concreta, pero un puerto arbitrario except : import sys print >>sys.stderr,"No podemos utilizar la direccion IP especificada: "+str(sys.exc_info()[1]) raise if correo : self.settimeout(30) socket.socket_old.connect(self,*args,**kwargs) if correo : self.settimeout(15*60) def __del__(self) : if hasattr(self,"lock_ip") : self.lock_ip.release() socket.socket=socket_new
Este código forma parte de mi proyecto "mmailer", un sistema masivo de envío de correo electrónico. No, no se usa para envíar Spam; estoy muy concienciado con el asunto y todo es muy legal, con contratos de por medio cumpliendo la LOPD.
La cuestión es que "mmailer" utiliza la librería estándar python "smtplib", que es la que instancia los "sockets".
Me interesan que esos sockets se conecten con el exterior utilizando una IP determinada, que se limiten a una conexión por máquina destino y que implementen "timeouts" razonables. De hecho quiero dos "timeouts" distintos: uno para el establecimiento de la conexión y otro para el intercambio de datos en sí.
No quiero modificar la "smtplib" para que instancie los objetos que me interesan. Pero, en cambio, puedo sobreescribir los objetos "socket" en memoria, reemplazando los normales, de forma que cuando la "smtplib" instancie sus "sockets" de toda la vida, en realidad esté instanciando los míos.
Y eso es lo que hace el código mostrado: creamos una clase nueva con el comportamiento que deseamos, y luego reemplazamos la original. En Python esta operación es simple y obvia; en otros lenguajes es sencillamente imposible :-).
Para modificar los "sockets" basta poner en nuestro código "import socket_decorator". A partir de ese momento, los "sockets" que se instancien utilizarán nuestro código, no el original.
Este código es para Python 2.5. No pretende realizar una sustitución "limpia", pero funciona y es bastante evidente. Sirva como ejemplo de la flexibilidad del lenguaje y de cómo hacer algo que, en otros lenguajes, obligaría a reimplementar la librería "smtplib".
¿Por qué no heredar de "smtplib" y alterar su comportamiento?. La razón principal es que el método "connect()" de "smtplib" es bastante sofisticado (por ejemplo, intenta conectar a las diferentes IPs que puede tener un mismo nombre de máquina). Si lo reimplemento, perdería esas funcionalidades, y si copio el código original, estaría dependiendo de una implementación concreta del objeto, que podría variar en versiones futuras de Python.
Más información sobre los OpenBadges
Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS