Reputation: 39013
I'm building a few decorators based on Django's @permission_required
decorator. These decorators simply call the permission_required decorator with different parameters.
For example:
def permission_required_ajax(perm):
return permission_required(perm, raise_exception=True)
This works well, and I have several such examples.
The problem is when my decorator accepts no arguments at all:
def special_permission():
return permission_required_ajax('special_permission')
This doesn't work. When I decorate a function with the @special_permission
decorator, I get the following error:
TypeError: special_permission() takes 0 positional arguments but 1 was given
What am I missing?
Upvotes: 3
Views: 304
Reputation: 114481
When a decorator has no parameters it gets passed the function/class being decorated. When it has parameters it's first called with the parameters and then whatever is returned is then called with the function/class being decorated.
To recap:
# Case 1, no arguments
@foo
def bar(): pass
The function foo
will be called being passed the function bar
and whatever is returned is then bound to the name bar
.
# Case 2
@foo(1, 2, 3)
def bar(): pass
The function foo
is called passing 1, 2 and 3 as parameters. What foo
returns is then called passing the bar
function as argument and what it returns is the bound to the name bar
.
A solution could be just adding parenthesis in the special_permission
decorator call...
@special_permission() # <-- note the parenthesis
def bar(): ...
Alternatively if you don't like adding the parenthesis in the use of the decorator it could be implemented as:
def special_permission(f):
return permission_required_ajax('special_permission')(f)
Upvotes: 1
Reputation: 1121744
You need to still call your decorator factory:
@special_permission()
def function():
# ...
Whatever the expression after the @
sign produces is used as the decorator; the decorator is called, passing in the function to decorate.
In other words, a decorator is syntactic sugar for:
def function():
# ...
special_permission()(function)
If you leave off the ()
call, the function is passed to your wrapper instead.
Alternatively, have your decorator accept the function, and pass it on directly to the decorator produced by the permission_required_ajax()
call:
def special_permission(func):
return permission_required_ajax('special_permission')(func)
and then use it without calling (it is now a decorator, not a decorator factory):
@special_permission # no () call now
def function():
# ...
Upvotes: 6