Decorator for both class methods and functions
Asked Answered
S

3

6

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.

Sober answered 17/3, 2016 at 17:28 Comment(3)
Not sure what is the problem here, if your decorator is a plain function, then you shouldn't have issues with decorating anything with it.Frap
When I use the decorator it gets also the "self" as parameter to the inner function. I want to keep my decorator generic enough to be able to use it also in functions which are not embedded in classes.Sober
I've updated the answer below. I guess you don't use params in the deco, otherwise, you would have to access them with args[index] where index could be shifted i.e. first actual argument for function has index 0 and for method it will be 1. You can use negative indices for simplicity but only if the number of arguments is constantFrap
F
6

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.

Frap answered 17/3, 2016 at 17:39 Comment(2)
just a question for the glory... there is a way to include the decorator action at the level of the parent Class? i.e. code a check at the parent level that is activated even when the method is called at the child levelSober
Not sure that I understand you correctly. You mean you would like to decorate base method so that overriding method from child class will be decorated too? No, it's not possible. From the Zen of Python: Explicit is better than implicit.. You must decorate explicitly every method/functionFrap
L
3

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)
Laugh answered 4/3, 2018 at 21:33 Comment(0)
C
0

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)
Caracole answered 27/4, 2024 at 9:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.