Reputation: 487
I just started learning decorators. I feel I understand how to modify a decorator to use arguments, however I don't see why the decorator in Python was implemented so that this modification is necessary.
[Edit: For clarity.] For a decorator without arguments, the below are equivalent:
@decorate
def function(arg):
pass
function = decorate(function)
So, for a decorator with arguments, why not let the below be equivalent?
@decorate(123)
def function(arg):
pass
function = decorate(function, 123)
I find this more consistent, easier to read, and easier to code.
Below is how python decorators work followed by how I expected them to work. [Edit: I added the expected output as well.]:
OUTPUT:
function HELLA NESTED! Decorated with OMG!
function NOT AS NESTED! Decorated with NOT SO BAD!
def python_decorator(dec_arg):
def actual_decorator(func):
def decorated_func(func_arg):
print(func.__name__, func(func_arg), "Decorated with", dec_arg)
return decorated_func
return actual_decorator
@python_decorator("OMG!")
def function(arg):
return arg
function("HELLA NESTED!")
def my_expected_decorator(func, dec_arg):
# I expected the `func` parameter to be like `self` in classes---it's
# always first and is given special meaning with decorators.
def decorated_func(func_arg):
print(func.__name__, func(func_arg), "Decorated with", dec_arg)
return decorated_func
# @my_expected_decorator("NOT SO BAD!") # This is what I expected.
def function(arg):
return arg
function = my_expected_decorator(function, "NOT SO BAD!")
function("NOT AS NESTED!")
I looked at PEP 318 and didn't see a reason why decorators were not implemented in the above way, but again I'm new to this idea. I feel the above change would make decorators easier and more consistent to use.
Upvotes: 4
Views: 238
Reputation: 43366
The reason why the current implementation was chosen is because it's more consistent. If it was implemented the way you suggested, python would have to handle decorators very differently depending on how they're used:
@python_decorator
, it can be called immediately with the function as the sole argument.@python_decorator("OMG!")
, python has to store those arguments somewhere and then insert the function as the first argument.@python_decorator()
, what should happen? Should this be equivalent to @python_decorator
? But then why are there two different ways to use a decorator with no arguments? Maybe that syntax shouldn't be allowed?As you can see, an implementation like this would add some inconsistencies. The chosen implementation on the other hand is really quite intuitive: The code after the @
symbol can be thought of as an expression, and the result of that expression is used as the decorator:
@python_decorator
, python_decorator
is an expression that simply returns an existing decorator function.@python_decorator("OMG!")
, the expression python_decorator("OMG!")
also returns a decorator function.In this sense, the chosen implementation is much more consistent than the one you proposed.
This rationale is also explained in the PEP:
The rationale for having a function that returns a decorator is that the part after the @ sign can be considered to be an expression (though syntactically restricted to just a function), and whatever that expression returns is called. See declaration arguments [16].
Regarding the suggestions voiced in the comments:
Banning the syntax @python_decorator
syntax and enforcing @python_decorator()
instead
The problem I see with this is that the parentheses serve no real purpose and only add clutter. The vast majority of decorators is applied without arguments. It's unreasonable to change the syntax for this just to be consistent with decorators that take arguments.
Explicitly passing the decorated function as the first argument
Again, this wouldn't add any semantic meaning. It's already obvious that the decorator will be called on the function that's defined directly below the @python_decorator
line. Forcing to user the explicitly pass the function as the argument is bad because
func = decorator(func)
syntax that had to be used before decorators existed. Having to write @decorator(func)
instead wouldn't really be a significant improvement, would it?Upvotes: 6