1

There are two ways to invoke decorators: 1) by passing decorator function/class

class Foo(object):
    def __init__(self):
        self.x = 1

    @foo_decorator
    def bar(self, x):
        return self.x + x

or 2) by passing return value of the decorator function/class instance.

class Foo(object):
    def __init__(self):
        self.x = 1

    @foo_decorator("args")
    def bar(self, x):
        return self.x + x

The question is: how do I get the decorator to work for both cases, given that I need to implement the decorator using class-based method?

An example of such implementation would be

import functools

class FooDecorator(object):
    def __init__(self, *args):
        if len(args) == 1 and callable(args[0]):
            self.f = args[0]
            functools.update_wrapper(self, args[0])
        else:
            self.f = None

    def __call__(self, *args, **kwargs):
        if self.f is not None:
            return self.f(*args, **kwargs)
        else:
            assert len(args) >= 1 and callable(args[0])
            f = args[0]
            self.f = f
            functools.update_wrapper(self, f)

            return self

foo_decorator = FooDecorator

However, testing this simple implementation using the following code yields some errors:

class Foo1(object):
    def __init__(self):
        self.x = 1

    @foo_decorator
    def bar(self, x):
        return self.x + x

class Foo2(object):
    def __init__(self):
        self.x = 1

    @foo_decorator("args")
    def bar(self, x):
        return self.x + x


Foos = [Foo1, Foo2]

for i in range(2):
    print(Foos[i].__name__)
    f = Foos[i]()
    print(f.bar(10))

Traceback (most recent call last):
  File "python", line 47, in <module>
  File "python", line 13, in __call__
TypeError: bar() missing 1 required positional argument: 'x'

This has been tested on both python 2.7 and 3.5.

Any help is appreciated. Note that I have already thoroughly searched the entire web, and have already read the following articles on the subject:

Also note that decorator package does not support the second method for incurring decorators (pre-initializing decorator objects) to my knowledge.

Kang Min Yoo
  • 805
  • 1
  • 9
  • 20
  • [*“Explicit is better than implicit”*](https://www.python.org/dev/peps/pep-0020/) – I would suggest you to decide whether or not your function is a decorator, or returns a decorator. If the argument is optional, make it `@foo_decorator("args")` and `@foo_decorator()`, but putting “magic” inside the call to figure out whether it’s used as a decorator or a function seems a bit too complicated (and difficult to maintain). – poke Mar 21 '17 at 08:24

1 Answers1

1

Reference and credits: http://www.ianbicking.org/blog/2008/10/decorators-and-descriptors.html

Class methods are (non-data) descriptors. If a method gets replaced during its decoration, the descriptor function must be restored.

This works for me on Python3. kwargs not implemented for simplicity.

class Deco:
    def __init__(self, *args):
        if len(args) == 1 and callable(args[0]):
            self._func = args[0]
            self._dargs = None
        else:
            self._func = None
            self._dargs = args

    def __call__(self, *args):
        if self._func is None:
            self._func = args[0]
            return self
        print("decorated with: {}".format(self._dargs))
        return self._func(*args)

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        def bound_decorated(*args):
            return self.__call__(obj, *args)
        return bound_decorated

(Anyway, I would prefer any simpler solution instead.)

VPfB
  • 14,927
  • 6
  • 41
  • 75