This question is rather specific, and I believe there are many similar questions but not exactly like this.
I am trying to understand syntactic sugar. My understanding of it is that by definition the code always can be written in a more verbose form, but the sugar exists to make it easier for humans to handle. So there is always a way to write syntactic sugar "without sugar" so to speak?
With that in mind, how precisely do you write a decorator without syntactic sugar? I understand it's basically like:
# With syntactic sugar
@decorator
def foo():
pass
# Without syntactic sugar
def foo():
pass
foo = decorator(foo)
Except from PEP 318
Current Syntax
The current syntax for function decorators as implemented in Python 2.4a2 is:
@dec2
@dec1
def func(arg1, arg2, ...):
pass
This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
without the intermediate assignment to the variable func. (emphasis mine)
In the example I gave above, which is how the syntactic sugar is commonly explained, there is an intermediate assignment. But how does the syntactic sugar work without the intermediate assignment? A lambda function? But I also thought they could only be one line? Or is the name of the decorated function changed? It seems like that could possibly conflict with another method if the user created one coincidentally with that same name. But I don't know which is why I'm asking.
To give a specific example, I'm thinking of how a property is defined. Since when defining a property's setter method, it cannot work if the setter method is defined as that would destroy the property.
class Person:
def __init__(self, name):
self.name = name
@property
def name(self):
return self._name
# name = property(name)
# This would work
@name.setter
def name(self, value):
self._name = value.upper()
# name = name.setter(name)
# This would not work as name is no longer a property but the immediately preceding method
Actually, the "non syntactic sugar" version, as you call it, is not exactly the same as using the decorator syntax, with an
@decorator:As you noted, when using the
@notation, the initial function name is never assigned a variable: the only assignment that takes place is for the resolved decorator.So:
What is assigned to
funcin theglobals()scope is the value returned by the call todeco.While in:
First
funcis assigned to the raw function, and just as thefunc=deco(func)line is executed the former is shadowed by the decorated result.The same apples for cascading decorators: only the final output, of the topmost decorator, is ever assigned to a variable name.
And, as well, the name used when using the
@syntax is taken from the source code - the name used in thedefstatement: if one of the decorators happen to modify the function__name__attribute, that has no effect in the assigned name for the decorated function.These differences are just implementation details, and derive of the way things work - I am not sure if they are on the language specs, but for those who have a certain grasp on the language, (1) they feel so natural, no one would dare implementing it differently, and (2) they actually make next to no difference - but for code that'd be tracing the program execution (a debugger, or some code using the auditing capabilities of the language (https://docs.python.org/3/library/audit_events.html )).
Despite this not being in other language specs, note however that the difference that the decorator syntax does not make the intermediate assignment is written down in PEP 318. Lacking other references, what is in the PEP is the law:
For sake of completeness, it is worth noting that from Python 3.10 (maybe 3.9), the syntax restriction that limited which expressions could be used as decorators after the
@was lifted, superseding the PEP text: any valid expression which evaluates to a callable can be used now.what about property ?
What takes place in this example, is that
name.setteris a callable, called with the setter method (def name(self, value):) defined bellow, as usual - but what happens is that it returns a property object. (Not the same as @name - a new property, but for the purpose of understanding what takes place, it could even return the same object).So that code is equivalent to:
In fact, while property was created before the decorator syntax (IRCC, in Python 2.2 - decorator syntax came out in Python 2.4) - it was not initially used as a decorator. The way one used to define properties in Python 2.2 times was:
It was only in Python 2.5 (or later) they made the clever ".setter" ".getter" and ".deleter" methods to properties so that it could be entirely defined using the decorator syntax.
Note that for properties with only a getter,
@propertywould work from Python 2.3 on, as it would just take the single parameter supplied by the decorator syntax to be the getter. But it was not extendable to have setter and deleter afterwards.