jojw85
jojw85

Reputation: 73

Decorator for both class methods and functions

I have a decorator

def deco(func):
    def inner(params):
        #< DO STUFF WITH func >
    return inner

And a base class

class GenericClass:
    def __init__(self,value):
        self.value = value
    def method(self,params):
        print 'NOT IMPLEMENTED YET'
    def other_method(self):
        print 'GOOD TO GO'

I would like to be able to decorate the "method" method on classes which are child of GenericClass, for exemple to check input/output or handle exceptions (the method "method" will be overrided)

what I want to do is something like...

class ChildClass(GenericClass):
    @deco
    def method(self,params):
        #< NEW METHOD >

I am not an expert python developper and all the doc at that level is quite confusing (i.e. metaclasses, subtleties in decorators, __call__ method etc etc) and I didn't found the solution on SO.

Upvotes: 7

Views: 6100

Answers (3)

Civil
Civil

Reputation: 79

pass a argument ignore_first

def test(ignore_first=False):
    def decorator(func):
        if not ignore_first:
            def _func(x):
                x*=10
                return func(x)
        else:
            def _func(_,x):
                x*=10
                return func(_,x)
        return _func
    return decorator

test passed:

def decorator_test():
    class A():
        @test(ignore_first=True)
        def f(self, x):
            print(x)
        @classmethod
        @test(ignore_first=True)
        def class_f(cls, x):
            print(x)
        @staticmethod
        @test()
        def static_f(x):
            print(x)
    a=A()
    @test()
    def f(x):
        print(x)
    def f1(x):
        @test()
        def f1_f(x):
            print(x)
        f1_f(x)
    a.f(11)
    a.class_f(22)
    a.static_f(33)
    f(44)
    f1(55)

Upvotes: 0

Amo
Amo

Reputation: 149

I figured it out. The trick is that module-level functions (except for closures, I guess, which you probably don't want to decorate anyway) have a simple name while methods at least have two parts in their qualified name. Forget about inspect.ismethod - for some reason it just won't work in this case, although it should be the obvious choice, possibly a bug.

def can(*fargs):
    def wrapper(func):
        if len(func.__qualname__.split('.')) > 1:
            def calling(self, *args, **kwargs):
                self, thing = args[0], args[1]
                do_stuff(thing)
                func(*args, **kwargs)
        else:
            def calling(*args, **kwargs):
                thing = args[0]
                do_stuff(thing)
                func(*args, **kwargs)
        return calling
    return wrapper

class C:
    @can(2, 3)
    def test(self, x):
        print(7, ismethod(self.test), x)

@can()
def test(x):
    print(8, ismethod(test), x)

c = C()
c.test(12)

test(8)

Upvotes: 4

Grief
Grief

Reputation: 2040

Got it. You are basically asking how to write a decorator which can be applied to both functions and methods. It's possible:

def deco(func):
    def inner(*args):
        print('DECORATED: args={}'.format(args))
        func(*args)
    return inner

class Class:
    @deco
    def method(self, param): print('PARAM is {}'.format(param))

@deco
def func(a, b, c): print('{} {} {}'.format(a, b, c))

Class().method('X')

func(1, 2, 3)

OUTPUT:

DECORATED: args=(<__main__.Class instance at 0x7f740c6297a0>, 'X')
PARAM is X
DECORATED: args=(1, 2, 3)
1 2 3

P.S.

One year later I found one useful post (which was asked 8 years ago) here: Using the same decorator (with arguments) with functions and methods. The approach described there will be useful if you are care of actual parameters of the decorated function.

Upvotes: 6

Related Questions