Estoy tratando de crear un decorador que se pueda definir en la clase y decore todo lo definido en ella. Primero, déjame mostrarte la configuración que ya obtuve según otras respuestas de SO:
import inspect # https://stackoverflow.com/a/18421294/577669 def log(func): def wrapped(*args, **kwargs): try: print("Entering: [%s]" % func) try: # https://stackoverflow.com/questions/19227724/check-if-a-function-uses-classmethod if inspect.ismethod(func) and func.__self__: # class method return func(*args[1:], **kwargs) if inspect.isdatadescriptor(func): return func.fget(args[0]) return func(*args, **kwargs) except Exception as e: print('Exception in %s : (%s) %s' % (func, e.__class__.__name__, e)) finally: print("Exiting: [%s]" % func) return wrapped class trace(object): def __call__(self, cls): # instance, owner): for name, m in inspect.getmembers(cls, lambda x: inspect.ismethod(x) or inspect.isfunction(x)): setattr(cls, name, log(m)) for name, m in inspect.getmembers(cls, lambda x: inspect.isdatadescriptor(x)): setattr(cls, name, property(log(m))) return cls @trace() class Test: def __init__(self, arg): self.arg = arg @staticmethod def static_method(arg): return f'static: {arg}' @classmethod def class_method(cls, arg): return f'class: {arg}' @property def myprop(self): return 'myprop' def normal(self, arg): return f'normal: {arg}' if __name__ == '__main__': test = Test(1) print(test.arg) print(test.static_method(2)) print(test.class_method(3)) print(test.myprop) print(test.normal(4)) Al eliminar el decorador @trace de la clase, este es el resultado:
123 static class myprop normal Al agregar el decorador @trace obtengo esto:
Entering: [<function Test.__init__ at 0x00000170FA9ED558>] Exiting: [<function Test.__init__ at 0x00000170FA9ED558>] 1 Entering: [<function Test.static_method at 0x00000170FB308288>] Exception in <function Test.static_method at 0x00000170FB308288> : (TypeError) static_method() takes 1 positional argument but 2 were given Exiting: [<function Test.static_method at 0x00000170FB308288>] None Entering: [<bound method Test.class_method of <class '__main__.Test'>>] Exiting: [<bound method Test.class_method of <class '__main__.Test'>>] class: 3 Entering: [<property object at 0x00000170FB303E08>] Exiting: [<property object at 0x00000170FB303E08>] myprop Entering: [<function Test.normal at 0x00000170FB308438>] Exiting: [<function Test.normal at 0x00000170FB308438>] normal: 4Conclusión de este ejemplo: los métodos init, normal, class y prop están todos correctamente instrumentados.
Sin embargo, el método estático no lo es.
Mis preguntas para este fragmento son:
¡Gracias!
Mi solución final:
import inspect from typing import Type from decorator import decorator @decorator def log(func, *args, **kwargs): try: print("Entering: [%s]" % func) return func(*args, **kwargs) finally: print("Exiting: [%s]" % func) def _apply_logger_to_class(cls): for attr in inspect.classify_class_attrs(cls): if attr.defining_class is not cls: continue if attr.kind == 'data': continue if isinstance(attr.object, (classmethod, staticmethod)): setattr(cls, attr.name, attr.object.__class__(log(attr.object.__func__))) elif isinstance(attr.object, property): setattr(cls, attr.name, property(log(attr.object.fget))) else: setattr(cls, attr.name, log(attr.object)) return cls def trace(func=None): if isinstance(func, type): return _apply_logger_to_class(func) # logger is the class return log(func) @trace def normal_function_call(arg): return f'normal_function_call: {arg}' @trace class Test: def __init__(self, arg): self.arg = arg print(f'{self.__class__.__name__}.__init__: {arg}') @staticmethod def static_method(arg): return f'Test.static: {1}' @classmethod def class_method(cls, arg): print(f'{cls.__name__}.class: {arg}') @property def myprop(self): print(f'{self.__class__.__name__}.myprop.getter') return 1 @myprop.setter def myprop(self, item): print(f'{self.__class__.__name__}.myprop.setter') @myprop.deleter def myprop(self): print(f'{self.__class__.__name__}.myprop.deleter') def normal(self, arg): print(f'{self.__class__.__name__}.normal: {arg}') @trace class TestDerived(Test): @staticmethod def static_method(arg): print(f'TestDerived.class: {arg}') if __name__ == '__main__': print(normal_function_call(0)) def do_test(test_class: Type[Test]): print('-'*20, test_class.__name__) test = test_class(1) test.static_method(2) test.__class__.static_method(2.5) test.class_method(3) test.__class__.class_method(3.5) test.myprop test.normal(4) assert inspect.getfullargspec(test.normal).args == ['self', 'arg'] assert inspect.getfullargspec(test.normal).kwonlyargs == [] do_test(Test) do_test(TestDerived)Esto funciona según lo previsto para las clases derivadas, para todos los objetos que quiero, y conserva las firmas. (que @wraps no lo hace).