def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
In the example [...] there is an intermediate assignment. But how does
the syntactic sugar work without the intermediate assignment?
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:
@deco
def func():
...
What is assigned to func
in the globals()
scope is the value returned by the call to deco
.
While in:
def func():
...
func = deco(func)
First func
is assigned to the raw function, and just as the func=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 the def
statement: 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:
This is equivalent to: [example without the @ syntax],
though without the intermediate creation of a variable named func.
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 ?
class Person:
...
@property
def name(self):
...
@name.setter
def name(self, value):
...
What takes place in this example, is that name.setter
is 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:
class Person:
...
def name(self):
...
name = property(name) # Creates a property with the getter only
def name_setter(self, value): # Indeed: this can't be called simply `name`: it would override the property created above
...
name = name.setter(name_setter) # Creates a new property, copying what already was set in the previous "name" and adding a setter.
del name_setter # optionally: removes the explicit setter from the class namespace, as it will always be called via the property.
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:
class Person:
...
def name_getter(self):
...
def name_getter(self):
...
name = property(name_getter, name_setter)
del name_getter, name_setter # optional
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, @property
would 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.