Derivacion de tipos, interceptar operaciones

Oswaldo Hernández listas en soft-com.es
Lun Mar 17 22:03:18 CET 2008


Chema Cortes escribió:
> El 17/03/08, Oswaldo Hernández <listas en soft-com.es> escribió:
> 
>> Buscando solucion a esto he encontrado otra discrepancia entre tipos y clases "normales". El
>>  __dict__ de una clase normal es un diccionario, mientras que en una clase derivada de un tipo es un
>>  objeto 'dictproxy', el cual no se puede utilizar directamente, o por lo menos yo no he encontrado como.
> 
> En realidad, más que discrepar, es una parte del mecanismo para poder
> unificar ambos conceptos (PEPs 252 y 253) con el que se implementa el
> protocolo del "descriptor" (por cierto, procotolo implementado gracias
> al método __getatribute__ del tipo 'object', progenitor de todas las
> clases nuevas).
> 
> El "dictproxy" funciona igual que un diccionario (protocolo
> "mapping"). Lo que pasa es que sus items están protegidos contra
> escritura, por lo que de poco te servirá.
> 
> http://www.python.org/dev/peps/pep-0252/
> http://www.python.org/dev/peps/pep-0253/

Los estuve viendo, igual que cafepy.com, pero he de confesar que llega un momento en que me pierdo, :(

> 
> Es muy complejo de explicar, por lo que te remito a estos PEPs y a los
> tutoriales que hay por http://cafepy.com (y, si te animas, a revisar
> el código C). Así, por encima, puedo contarte que los atributos
> "estáticos" (aquellos que obtiene la clase por ser un "tipo de dato")
> se implementan mediante "slots". Estos slots apuntan inicialmente a
> métodos facilitados por el tipo de dato, y son sustituídos por los que
> suplanta la herencia (atributos dinámicos). Los métodos estáticos son
> mucho más rápidos de ejecutar por el intérprete, evitando la
> sobrecarga que impone una herencia pura con las operaciones básicas
> estándar. Supongo que, por éso mismo, python resulta bastante más
> rápido que smalltalk y lenguajes similares.
> 
> La única solución para tu problema sería crear "wrappers" para todos y
> cada uno de los métodos estáticos que tenga el tipo de dato, algo como
> se cuenta en esta receta:
> 
> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496741

Muy, muy, muy interesante, aunque no es lo mismo, se parece bastante a mi proyecto. Lo revisare con 
detenimiento.
Sobre los slots habia visto algo, pero lo volveré a revisar.


Mi necesidad es filtrar las instancias antes de que se realice cualquier tipo de operacion con 
ellas, y en ciertos casos impedir la operacion lanzando un ValueError.

Un resumen de lo que he hecho he hecho es lo siguiente:

1.- Definicion de las clases con un metodo __new__ comun

# esta funcion es llamada en el new de las bases para verificar valores y creacion de instancia
def _Super_New_(cls, *args):
     # verificacion de rango
     if cls.Rango and args[0] < cls.Rango[0] or args[0] > cls.Rango[1]:
         raise ValueError
     ...otros chequeos y adaptaciones ...

     return = cls.__base__.__new__(cls, *args)

# Clases principales
class c_int(int):
     Valido = True	# en ciertos casos me interesa no dar error
			# y marcar la instancia como 'No Valida'
     Rango = None
     def __new__(cls, *args):
         return _Super_new_(cls, *args)

class c_date(datetime.date):
     Valido = True
     Rango = None
     def __new__(cls, *args):
         return _Super_new_(cls, *args)


2.- Añadir metodos sobrescritos

Para añadir los metodos sobreescritos, sin el engorro y la dificultad de mantenimiento posterior de 
poner uno por uno en cada clase, he hecho lo siguiente:

Primero, un SuperMetodo que filtrará todas las operaciones:

def __SuperMetodo(self, op, *args):
     if not self.Valido:
         raise ValueError
     # verificar si other es mio y si es valido tambien
     if hasattr(args[0], "Valido") and not args[0].Valido:
         raise ValueError
     # ejecutar la operacion en base
     r = getattr(self.__base__, op)(self, *args, **kwds)
     ... Otras verificaciones ....
     return r

A nivel de modulo, para que se ejecute cuando se realice el import:

Operadores = ("__add__", "__sub__", ....)
Clases = (m_int, m_date, ...)

Ahora tengo que sobreescribir el metodo de operadores en cada clase para que llamen al __SuperMetodo 
pasandole como argumento la instancia, la operacion a realizar y sus argumentos, y este, si lo cree 
conveniente, ejecute el metodo en la base.

Aqui es donde entra mi problema con el __dict__, no puedo utilizar exec() ni new.Function() para 
insertar directamente los metodos con su codigo en las clases porque a ambas hay que pasarles el 
diccionario y rechazan el 'dictproxy'.

Para solucionar esto he hecho lo siguiente:

Creo Metodos genericos para todas las clases en globals()

for op in  in Operadores:
     exec "def %sz(self, *args, **kwds): return __SuperMetodo(self, '%s', *args, **kwds)" % (op, op) 
in globals()

Creo los metodos en las clases son setattr() asignandolos a los creados en globals()

for c in Clase :
     for m in Metodos:
         if hasattr(c , op):
             setattr(c, op, globals()["%sz" % op])



Bueno, discupad el rollo, pero llevo un monton de horas peleandome con esto.

Parace que ya funciona :), pero si alguin tiene una idea mejor de como hacerlo, bienvenida es.


Saludos,
-- 
*****************************************
Oswaldo Hernández
oswaldo (@) soft-com (.) es
*****************************************
PD:
Antes de imprimir este mensaje, asegúrese de que es necesario.
El medio ambiente está en nuestra mano.
_______________________________________________
Lista de correo Python-es 
http://listas.aditel.org/listinfo/python-es
FAQ: http://listas.aditel.org/faqpyes





Más información sobre la lista de distribución Python-es