Welcome, guest | Sign In | My Account | Store | Cart

A decorator factory is a function that returns a decorator based on the arguments you pass in. Sometimes you make a decorator factory to cover uncommon use cases. In that case you'll probably use default arguments to cover your common case.

The problem with this, however, is that you still have to use the call syntax (with no arguments) to get the common-use-case decorator, as demonstrated in the recipe title. This recipe effectively makes it so that your decorator factory and your common-use-case decorator have the same name (and actually the same object).

Python, 31 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"""decorator module

"""

import functools


def has_default_decorator(decorator_factory):
    """A meta-decorator for decorator factories.

    This decorator of decorators allows a decorator factory to be used
    as a normal decorator (without calling syntax).

    A single non-keyword argument (with no keyword arguments) will be
    considered the object to be decorated.  If more than one argument
    is passed, or any keyword arguments are passed, all arguments will
    be passed to the decorator factory.

    To treat a single argument as an argument to the decorator factory,
    pass the keyword-only argument "lonely_argument=True".  It will
    default to False.

    """

    @functools.wraps(decorator_factory)
    def wrapper(*args, **kwargs):
        single_argument = kwargs.pop("lonely_argument", False)
        if not single_argument and len(args) == 1:
            return decorator_factory()(*args)
        return decorator_factory(*args, **kwargs)
    return wrapper
# example

import inspect

@has_default_decorator
def has_typed_arg(checktype=None):
    builtins = [value for name, value in __builtins__.__dict__.items()
                      if not name.startswith("_") and type(value) is type]
    def decorator(f):
        def newfunc(*args, **kwargs):
            for i in range(len(args)):
                argtype = type(args[i])
                if checktype is None and argtype in builtins:
                    print("arg {} matched {}".format(i, argtype))
                if checktype is not None and type(args[i]) is checktype:
                    print("arg {} matched {}".format(i, argtype))
            for name, arg in kwargs.items():
                if checktype is None and argtype in builtins:
                    print("arg {} matched {}".format(name, argtype))
                if checktype is not None and type(arg) is checktype:
                    print("arg {} matched {}".format(name, argtype))
            return f(*args, **kwargs)
        return newfunc
    return decorator

@has_typed_arg
@has_typed_arg(str, lonely_argument=True)
def test(a, b, c):
    pass

test(1, 2, 3)
print("---------")
test("a", None, None)
print("---------")
class X(object): pass
test(X(), X(), X())