Alex Rivera | Logout

Making decorators with optional arguments

Asked 2010-10-08T06:22:21.107
79
from functools import wraps

def foo_register(method_name=None):
    """Does stuff."""
    def decorator(method):
        if method_name is None:
            method.gw_method = method.__name__
        else:
            method.gw_method = method_name
        @wraps(method)
        def wrapper(*args, **kwargs):
            method(*args, **kwargs)
        return wrapper
    return decorator

Example: The following decorates my_function with foo_register instead of ever making it to decorator.

@foo_register
def my_function():
    print('hi...')

Example: The following works as expected.

@foo_register('say_hi')
def my_function():
    print('hi...')

If I want it to work correctly in both applications (one using method.__name__ and one passing the name in), I have to check inside of foo_register to see if the first argument is a decorator, and if so, I have to: return decorator(method_name) (instead of return decorator). This sort of "check to see if it's a callable" seems very hackish. Is there a nicer way to create a multi-use decorator like this?

P.S. I already know that I can require the decorator to be called, but that's not a "solution". I want the API to feel natural. My wife loves decorating, and I don't want to ruin that.

Edit
Report

1 Answer

4

Now that this old thread is back at the top anyway, lemme just throw in some Decorator-ception:

def magical_decorator(decorator):
    @wraps(decorator)
    def inner(*args, **kw):
        if len(args) == 1 and not kw and callable(args[0]):
            return decorator()(args[0])
        else:
            return decorator(*args, **kw)
    return inner

Now your magical decorator is just a single line away!

@magical_decorator
def foo_register(...):
    # bla bla

By the way, this works for any decorator. It just causes @foo to behave (as close as possibly) like @foo().

answered 2012-04-23T21:59:31.613

Your Answer