Introduction

Sometime you need a decorator with these properties: Value:

# a simple un-adorned decorator (form-1)
@decorator
def function():
  <span style="color:green" class="x-dest-value1">x</span>
  {{x:=}}
  ...


# same decorator but with no arguments (form-2)
@decorator()
def function():
  ...


# and finally a decorator with parameters and arguments (form-3)
@decorator(1, 2, a=3, b=4)
def function():
  ...

NOTE there is one form that cannot be correctly implemented:

   @decorator(callable)
   def function():
      ...

Unfortunately this decorator form is ambiguous and cannot be handled.

Below the description how to create such a decorator (TL;DR example.py)

Use

This is the function we're going to decorate:

def function(x, y):
    return x+y**2

>>> print(function(1, 2))
5

form-1

This is how to wrap around the function with decorator, let's say doubling the function result by 2:


@decorator
def function(x, y):
    return x+y**2


>>> print(fn(1, 2))
10 # eg. ( 1 + 2**2 ) * 2

form-2

In this form we want decorator to retunr the function result by 3:


@decorator()
def fn(x, y):
    ...


>>> print(fn(1, 2))
15 # eg. ( 1 + 2**2 ) * 3

form-3

Here decorator get the scale factor from one of its argument (eg. b=4)


@decorator(1, 2, a=3, b=4)
def fn(x, y):
    ...


>>> print(fn(1, 2))
20 # eg. ( 1 + 2**2 ) * 4

decorator implementation

The decorator (covering all the three forms) look like:

class decorator(Decorator):
    def call(self, config, function, *args, **kwargs):
        if config is None:
            factor = 2              # form-1 config is None
        elif not config[0]:
            factor = 3              # form-2 config is ((), {}) 2-tuple
        else:
            factor = config[1]["b"] # form-3 config is ((1, 2), { "a" : 3, "b": 4 }) 2-tuple

        return function(*args, **kwargs) * factor

NOTE we use class approach more elegant and clearer

Decorator base class implementation

This is how the base class Decorator is implemented:

class Decorator:
    def __init__(self, *args, **kwargs):
        from functools import update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
        self._function = None
        self._config = None
        if (not kwargs) and (len(args) == 1) and callable(args[0]):
            self._function = args[0]
            update_wrapper(
                self,
                self._function,
                assigned=WRAPPER_ASSIGNMENTS,
                updated=WRAPPER_UPDATES)
        else:
            self._config = (args, kwargs)

    def __call__(self, *args, **kwargs):
        if self._function:
            return self.call(self._config, self._function, *args, **kwargs)
        else:
            obj = self.__class__(*args, **kwargs)
            obj._config = self._config
            return obj

    def call(self, config, function, *args, **kwargs):
        return function(*args, **kwargs)