Ali SAID OMAR
Ali SAID OMAR

Reputation: 6792

Apply decorator to all method of sub classes for timeit

I have a method decorator looking like

def debug_run(fn):

    from functools import wraps

    @wraps(fn)
    def wrapper(self, *args, **kw):
        # log some stuff
        # timeit fn
        res = fn(self, *args, **kw)
    return wrapper

Right now I used to use it apply on each method that I want to debug. Now i'm trying to apply to all class method using a class decorator looking like.

Rather doing

class A():
    @debug_run
    def f(self):
       pass

I do

@decallmethods(debug_run)
class A():

    def f(self):
       pass

def decallmethods(decorator):
    def dectheclass(cls):
        for name, m in inspect.getmembers(cls, inspect.ismethod):
            if name in getattr(cls, 'METHODS_TO_INSPECT', []):
                setattr(cls, name, decorator(m))
        return cls
    return dectheclass

Trying to apply to decorator to the base class, not working as expected. no log to the console. Now i wonder if this approach is the good or I should used something else (apply the debug decorator to selected method from base class to all sub classes).

[EDIT]

Finally found why no logs were printed

Why is there a difference between inspect.ismethod and inspect.isfunction from python 2 -> 3?

Here a complete example reflecting my code

import inspect
import time

import logging as logger
from functools import wraps

logger.basicConfig(format='LOGGER - %(asctime)s %(message)s', level=logger.DEBUG)


def debug_run(fn):

    @wraps(fn)
    def wrapper(self, *args, **kw):
        logger.debug(
            "call method %s of instance %s with %r and %s "
            % (fn.__name__, self, args, kw))
        time1 = time.time()
        res = fn(self, *args, **kw)
        time2 = time.time()
        logger.debug(
            "%s function %0.3f ms" % (fn, (time2-time1)*1000.0))
        return res
    return wrapper


def decallmethods(decorator):
    def dectheclass(cls):
        for name, m in inspect.getmembers(
                cls, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x)):
            methods_to_inspect = getattr(cls, 'METHODS_TO_INSPECT', [])

            if name in methods_to_inspect:
                setattr(cls, name, decorator(m))
        return cls
    return dectheclass


class B(object):
    METHODS_TO_INSPECT = ["bfoo1", "bfoo2", "foo"]

    def __str__(self):
        return "%s:%s" % (repr(self), id(self))

    def bfoo1(self):
        pass

    def bfoo2(self):
        pass

    def foo(self):
        pass

    def run(self):
        print("print - Base run doing nothing")


class C(object):
    pass


@decallmethods(debug_run)
class A(B, C):
    METHODS_TO_INSPECT = ["bfoo1", "bfoo2", "foo", "run"]

    def foo(self):
        print("print - A foo")

    def run(self):
        self.bfoo1()
        self.bfoo2()
        self.foo()


a = A()
b = B()

a.run()
b.run()

In this case applying decallmethods to B, will not affect the A so i must to apply to both A and B thus to all sub classes of B.

It is possible to have such mechanism that permit to apply decallmethods to all sub classes methods ?

Upvotes: 0

Views: 663

Answers (1)

Jonathan
Jonathan

Reputation: 496

look at this: How can I decorate all functions of a class without typing it over and over for each method added? Python

delnan has a good answer, only add this rule to his answer

if name in getattr(cls, 'METHODS_TO_INSPECT', []):

Upvotes: 1

Related Questions