Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python decorator for class OR function

Just soliciting opinion on whether the following is reasonable or if there is a better approach. Basically I want a decorator that will apply to a function or a class that implements __call__.

You could just have a regular decorator and decorate the __call__ explicitly but then the decorator is tucked inside the class definition and is less obvious. Maybe I am missing a more straightforward solution.

import types
from functools import wraps

class dec:
    """ Decorates either a class that implements __call__ 
        or a function directly.
    """
    def __init__(self, foo):
        self._foo = foo

    def __call__(self, target):
        wraps_class = isinstance(target, types.ClassType)
        if wraps_class:
            fun = target.__call__
        else:
            fun = target

        @wraps(fun)
        def bar(*args, **kwds):
            val = args[1] if wraps_class else args[0]
            print self._foo, val
            return fun(*args, **kwds)
        if wraps_class:
            target.__call__ = bar
            return target
        else:
            return bar

@dec('A')
class a:
    # you could decorate here, but it seems a bit hidden
    def __call__(self, val):
        print "passed to a:", val

@dec('B')
def b(val):
    print "passed to b:", val

a()(11)
b(22)
like image 559
David Schein Avatar asked Feb 15 '12 14:02

David Schein


People also ask

Can Python decorators be used on classes?

In Python, decorators can either be functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class.

How do you implement a decorator in a class Python?

To decorate a function with a class, we must use the @syntax followed by our class name above the function definition. Following convention, we will use camel-case for our class name. In the class definition, we define two methods: the init constructor and the magic (or dunder) call method.

Can a class method be a decorator?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.

What is decorator in Python for function?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.


2 Answers

Personally, I would split this into two decorators: one that always wraps a function:

def func_dec(foo, is_method=False):
    def wrapper(fun):
        @wraps(fun)
        def bar(*args, **kwds):
            val = args[1] if is_method else args[0]
            print foo, val
            return fun(*args, **kwds)
        return bar
    return wrapper

And another that detects if it should modify a __call__ method or simply wrap a function:

def dec(foo):
    def wrapper(obj):
        if inspect.isclass(obj):
            obj.__call__ = func_dec(foo, is_method=True)(obj.__call__)
            return obj
        else:
            return func_dec(foo)(obj)
    return wrapper

Note that inspect.isclass will behave correctly with both old-style and new-style classes.

like image 134
DzinX Avatar answered Oct 26 '22 03:10

DzinX


I don't really like your approach. The __call__() method is used if an instance is called. Calling the class itself invokes __init__() instead, so I don't see this to be really analogous.

Your decorator won't work for new-style classes (directly or indirectly derived from object). Do yourself a favour and simply decorate __call__() if this is what you want. Or write a factory function that creates and decorates instances of the class -- this would be in total analogy to decorating a function, because the instance is directly callable, and you don't have to mess arounf with the self parameter.

like image 34
Sven Marnach Avatar answered Oct 26 '22 02:10

Sven Marnach