Python: Modificar los argumentos pasados ​​antes de la inicialización de la instancia de clase

Estoy experimentando con formas de implementar un sistema simplificado de reescritura de términos (TRS) / sistema de álgebra simbólica en Python. Para esto realmente me gustaría poder interceptar y modificar los operandos en casos particulares durante el proceso de creación de instancias de instancia de clase. La solución que se me ocurrió fue crear una metaclase que modifique el comportamiento de llamada típico de un objeto de clase (de tipo ‘tipo’).

class Preprocess(type): """ Operation argument preprocessing Metaclass. Classes using this Metaclass must implement the _preprocess_(*operands, **kwargs) classmethod. """ def __call__(cls, *operands, **kwargs): pops, pargs = cls._preprocess_(*operands, **kwargs) return super(Preprocess, cls).__call__(*pops, **pargs) 

Un ejemplo sería expandir las operaciones anidadas F (F (a, b), c) -> F (a, b, c)

 class Flat(object): """ Use for associative Operations to expand nested expressions of same Head: F(F(x,y),z) => F(x,y,z) """ __metaclass__ = Preprocess @classmethod def _preprocess_(cls, *operands, **kwargs): head = [] for o in operands: if isinstance(o, cls): head += list(o.operands) else: head.append(o) return tuple(head), kwargs 

Entonces, ahora este comportamiento se puede realizar a través de la herencia:

 class Operation(object): def __init__(self, *operands): self.operands = operands class F(Flat, Operation): pass 

Esto conduce al comportamiento deseado:

 print F(F(1,2,3),4,5).operands (1,2,3,4,5) 

Sin embargo, me gustaría combinar varias de estas clases de preprocesamiento y hacer que procesen los operandos de forma secuencial de acuerdo con la clase natural mro.

 class Orderless(object): """ Use for commutative Operations to bring into ordered, equivalent form: F(*operands) => F(*sorted(operands)) """ __metaclass__ = Preprocess @classmethod def _preprocess_(cls, *operands, **kwargs): return sorted(operands), kwargs 

Y esto no parece funcionar como se quería. Definición de un tipo de operación plana y sin orden

 class G(Flat, Orderless, Expression): pass 

da como resultado que solo la primera superclase de preprocesamiento esté ‘activa’.

 print G(G(3,2,1),-1,-3).operands (3,2,1,-1,-3) 

¿Cómo puedo asegurarme de que todos los métodos de preproceso de las clases de preprocesamiento sean llamados antes de la creación de instancias de clase?

Actualizar:

Parece que todavía no puedo responder formalmente a mi pregunta debido a mi estado como nuevo usuario de stackoverflow. Entonces, creo que esta es probablemente la mejor solución que puedo encontrar:

 class Preprocess(type): """ Abstract operation argument preprocessing class. Subclasses must implement the _preprocess_(*operands, **kwargs) classmethod. """ def __call__(cls, *operands, **kwargs): for cc in cls.__mro__: if hasattr(cc, "_preprocess_"): operands, kwargs = cc._preprocess_(*operands, **kwargs) return super(Preprocess, cls).__call__(*operands, **kwargs) 

Supongo que el problema es que super(Preprocess, cls).__call__(*operands, **kwargs) no atraviesa el mro de cls como se esperaba.

Creo que estás haciendo esto de manera equivocada; suelta la metaclase, usa en su lugar decoradores de clases y métodos

Por ejemplo, define tu piso como:

 @init_args_preprocessor def flat(operands, kwargs): # No need for asterisks head = [] for o in operands: if isinstance(o, cls): head += list(o.operands) else: head.append(o) return tuple(head), kwargs 

Con el decorador init_args_preprocessor convirtiendo esa función en un decorador de clase:

 def init_args_preprocessor(preprocessor): def class_decorator(cls): orig_init = cls.__init__ def new_init(self, *args, **kwargs): args, kwargs = preprocessor(args, kwargs) orig_init(self, *args, **kwargs) cls.__init__ = new_init return cls return class_decorator 

Y ahora, en lugar de usar mixins decoradores:

 class Operation(object): def __init__(self, *operands): self.operands = operands @flat class F(Operation): pass 

Y no debería tener problemas al combinar los modificadores de clase de forma limpia:

 @init_args_preprocessor def orderless(args, kwargs): return sorted(args), kwargs @orderless @flat class G(Expression): pass 

Caveat Emptor: Todo el código de arriba estrictamente no probado.