Python etc / Decorator syntax

Decorator syntax

Famous Python decorator syntax (@this_one) is a way to call higher-order function. Back then people had to do it manually:

# prior to Python 2.4
def query():
    pass
query = atomic(query)

# now
@atomic
def query():
    pass

Basically, the identifier after @ is what to be called. You also can use identifier with brackets (@atomic(skip_errors=True)), that is usually used for parameterized decorators. Something like @decorators.db.atomic(True) also works. Looks like you use any kind of expression as a decorator, but that is not true. @ must be followed by one “dotted named” (meaning something like decorators.atomic) and optionally by one pair of brackets with arguments (just like a function call). So, no @decorators[2] for you. Here is a line from Python grammar:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE

Parameterized decorators

Even if you use identifier with brackets as a decorator (@atomic()), it will be called again with a function as an argument: query = atomic(skip_errors=True)(query). There are tons of examples out there on how to create parameterized decorators, let's just move on and write a decorator that has no parameters, but can be called with empty brackets ().

def atomic(func=None):
    if func is None:
        return atomic
    @functools.wraps(func)
    def wrapped():
        print('BEGIN')
        func()
        print('COMMIT')

    return wrapped

@atomic()
def query():
    print('q')

You can even extract that logic by decorating a decorator:

def unparameterized_decorator(decorator):
    @functools.wraps(decorator)
    def wrapped(func=None):
        if func is None:
            return decorator
        return decorator(func)

    return wrapped

@unparameterized_decorator
def atomic(func):
    @functools.wraps(func)
    def wrapped():
        print('BEGIN')
        func()
        print('COMMIT')

    return wrapped

@atomic()
def query():
    print('q')
Try this code live