简体   繁体   中英

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)

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.

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.

That's a pretty slick idea. It seems fine to me, although it might be more pythonic to decorate __call__ directly since "explicit is better than implicit". There's a little bit of conceptual overhead to having one decorator that does two things.

(I wonder if it would be worse or better to make a decorator that takes any function decorator and turns it into a dual function/class decorator...)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM