Decorar un método de clase después de @property

Quiero envolver todos los métodos de varios objetos excepto __init__ usando un decorador.

 class MyObject(object): def method(self): print "method called on %s" % str(self) @property def result(self): return "Some derived property" def my_decorator(func): def _wrapped(*args, **kwargs): print "Calling decorated function %s" % func return func(*args, **kwargs) return _wrapped class WrappedObject(object): def __init__(self, cls): for attr, item in cls.__dict__.items(): if attr != '__init__' and (callable(item) or isinstance(item, property)): setattr(cls, attr, my_decorator(item)) self._cls = cls def __call__(self, *args, **kwargs): return self._cls(*args, **kwargs) inst = WrappedObject(MyObject)() 

Sin embargo, el ajuste de los resultados de una instancia de propiedad es equivalente a esto:

 @my_decorator @property def result(self): return "Some derived property" 

Cuando el resultado deseado es algo equivalente a esto:

 @property @my_decorator def result(self): return "Some derived property" 

Parece que los atributos de un objeto de propiedad son de solo lectura, lo que impide modificar la función después de que la propiedad lo haya cerrado. No estoy muy cómodo con el nivel de piratería requerido y prefiero no profundizar en el objeto de la propiedad de todos modos.

La única otra solución que puedo ver es generar una metaclase sobre la marcha que esperaba evitar. ¿Me estoy perdiendo algo obvio?

Hay algunos otros problemas en este ejemplo, pero para cuestionarlo, todo lo que tiene que hacer es, cuando está envolviendo una propiedad.

Cuando esté envolviendo una propiedad, envuelva su método __get__ en su lugar:

 class MyObject(object): def method(self): print "method called on %s" % str(self) @property def result(self): return "Some derived property" def common(self, a=None): print self def my_decorator(func): def _wrapped(*args, **kwargs): print "Calling decorated function %s" % func return func(*args, **kwargs) return _wrapped class WrappedObject(object): def __init__(self, cls): for attr, item in cls.__dict__.items(): if attr != '__init__' and callable(item): setattr(cls, attr, my_decorator(item)) elif isinstance(item, property): new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__) setattr(cls, attr, new_property) self._cls = cls def __call__(self, *args, **kwargs): return self._cls(*args, **kwargs) inst = WrappedObject(MyObject)() 

Esa es la simple modificación de su código que hace el trabajo. Sin embargo, lo cambiaría dinámicamente a una subclase de las clases que está envolviendo, para evitar volver a escribir sus atributos. Puede crear una subclase programáticamente simplemente calificando el tipo con el nombre, una tupla con las bases y un dict como parámetros.

editar – cambiar el código a la clase envuelta subclase

En realidad, la subclase de la clase dada no requiere casi ninguna modificación en el código dado, pero para la llamada de type que indiqué. Acabo de probarlo aquí: cambia tu clase WrappedObject a:

 class WrappedObject(object): def __init__(self, cls): dct = cls.__dict__.copy() for attr, item in dct.items(): if attr != '__init__' and callable(item): dct[attr] = my_decorator(item) elif isinstance(item, property): new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__) dct[attr] = new_property self._cls = type("wrapped_" + cls.__name__, (cls,), dct) def __call__(self, *args, **kwargs): return self._cls(*args, **kwargs) 

Después de un poco de prueba y error, se me ocurrió la siguiente solución. Primero, crea una clase de ayuda que emule un descriptor decorado:

 class DecoratedDescriptor(object): def __init__(self, descriptor, decorator): self.funcs = {} for attrname in '__get__', '__set__', '__delete__': self.funcs[attrname] = decorator(getattr(descriptor, attrname)) def __get__(self, *args, **kwargs): return self.funcs['__get__'](*args, **kwargs) def __set__(self, *args, **kwargs): return self.funcs['__set__'](*args, **kwargs) def __delete__(self, *args, **kwargs): return self.funcs['__delete__'](*args, **kwargs) 

Entonces, si ves una propiedad, envuélvela en ella:

 # Fragment of your WrappedObject.__init__ method: if attr != '__init__' and callable(item): setattr(cls, attr, my_decorator(item)) elif isinstance(item, property): setattr(cls, attr, DecoratedDescriptor(item, my_decorator)) 

Esto funciona así:

 >>> inst = WrappedObject(MyObject)() >>> print inst.result Calling decorated function  Some derived property 

¡Ahí! No hay metaclases 🙂

Podría introducir decoradores “perezosos”, que se aplican después de su propio decorador, por ejemplo:

 class Lazy(object): def __init__(self, decorator): self.decorator = decorator def __call__(self, method): self.method = method return self def my_decorator(func): def _wrapped(*args, **kwargs): print "Calling decorated function %s" % func return func(*args, **kwargs) if isinstance(func, Lazy): lazy = func func = lazy.method return lazy.decorator(_wrapped) return _wrapped lazy_property = Lazy(property) 

@lazy_property luego usa @lazy_property lugar de @property . (Advertencia: código no probado, pero espero que tenga la idea …)