Using python decorator with or without parentheses
some_decorator
in the first code snippet is a regular decorator:
@some_decorator
def some_method():
pass
is equivalent to
some_method = some_decorator(some_method)
On the other hand, some_decorator
in the second code snippet is a callable that returns a decorator:
@some_decorator()
def some_method():
pass
is equivalent to
some_method = some_decorator()(some_method)
As pointed out by Duncan in comments, some decorators are designed to work both ways. Here's a pretty basic implementation of such decorator:
def some_decorator(arg=None):
def decorator(func):
def wrapper(*a, **ka):
return func(*a, **ka)
return wrapper
if callable(arg):
return decorator(arg) # return 'wrapper'
else:
return decorator # ... or 'decorator'
pytest.fixture
is a more complex example.
Briefly speaking, decorators allow adding rich features to groups of functions and classes without modifying them at all.
The key to understand the difference between @some_decorator
and @some_decorator()
is that the former is decorator, while the latter is a function (or callable) that returns a decorator.
I believe that seeing an implementation of each case facilitates understanding the difference:
@some_decorator
def some_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
Application:
@some_decorator
def some_method():
pass
Equivalence:
some_method = some_decorator(some_method)
@some_decorator()
def some_decorator():
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
Application:
@some_decorator()
def some_method():
pass
Equivalence:
some_method = some_decorator()(some_method)
Notice that now it is easier to see that @some_decorator()
is a function returning a decorator while some_decorator
is just a decorator. Keep in mind that some decorators are written to work both ways.
So now you might be wondering why we have these two cases when the former version seems simpler. The answer is that if you want to pass arguments to a decorator, using @some_decorator()
will allow you to do this. Let's see some code in action:
def some_decorator(arg1, arg2):
def decorator(func):
def wrapper(*args, **kwargs):
print(arg1)
print(arg2)
return func(*args, **kwargs)
return wrapper
return decorator
Application:
@some_decorator('hello', 'bye')
def some_method():
pass
Equivalence:
some_method = some_decorator('hello', 'bye')(some_method)
Note: I think that it is worth to mention that a decorator can be implemented as a function or as a class. Check this for more information.