[Python-ideas] Python Decorator Improvement Idea
Steven D'Aprano
steve at pearwood.info
Sat Jun 16 01:22:10 EDT 2018
On Fri, Jun 15, 2018 at 11:54:42PM -0400, Brian Allen Vanderburg II via Python-ideas wrote:
> An idea I had is that it could be possible for a decorator function to
> declare a parameter which, when the function is called as a decorator,
> the runtime can fill in various information in the parameters for the
> decorator to use.
We can already do this, by writing a decorator factory:
@decorator(any parameters you care to pass)
def spam():
...
"Explicit is better than implicit" -- it is better to explicitly pass
the parameters you want, than to hope that "the runtime" (do you mean
the interpreter?) will guess which parameters you need.
> Some of the information would be available in all
> contexts, while other information may only be available in certain
> contexts.The parameter's value cannot be explicitly specified, defaults
> to Null except when called as a decorator, and can only be specified
> once in the function's parameter list.
Do you mean None?
Why do you think it is a good idea to have the same function, the
decorator, behave differently when called using decorator syntax and
standard function call syntax? To me, that sounds like a terrible idea.
What advantage do you see?
[...]
> Rules:
>
> 1. It is not possible for the parameter's value to be directly
> specified. You can't call fn(info=...)
That sounds like a recipe for confusion to me. How would you explain
this to a beginner?
Aside from the confusion that something that looks like a parameter
isn't an actual parameter, but something magical, it is also very
limiting. It makes it more difficult to use the decorator, since now it
only works using @ syntax.
> 2. The parameters value is Null except in the cases where it is invoked
> (the callable called a a decorator). If used in a partial, the
> decorator parameter would be Null. etc.
You keep saying Null. What's Null?
> Information that could be contained in the parameters for all contexts:
>
> Variable name
> Module object declared in
> Module globals (useful for @export/@public style decorators)
> Etc
The variable name is just the name of the function or class, the first
parameter received by the decorator. You can get it with func.__name__.
The module globals is already available in globals(). You can either
pass it directly as an argument to the decorator, or the decorator can
call it itself. (Assuming the decorator is used in the same module it is
defined in.)
If the decorator is in the same module as the globals you want to
access, the decorator can just call globals(). Or use the global
keyword.
If the decorator is contained in another module, the caller can pass the
global namespace as an argument to the decorator:
@decorate(globals())
def func(): ...
Not the neatest solution in the world, but it works now.
> Using the decorator in a class context, pass the class object.
The decorator already receives the class object as the first parameter.
Why pass it again?
> While the class object hasn't been fully created yet,
What makes you say that?
> this could allow
> accessing attributes of the class (like a registry or such)
>
> def decorator(fn, @info):
> if hasattr(info, "class_obj"):
> registry = info.class_obj.__dict__.setdefault("_registry", [])
>
> registry.append(fn)
> return fn
Writing "hasattr(info, whatever)" is an anti-pattern.
By the way, the public interface for accessing objects' __dict__ is to
call the vars() function:
vars(info.class_obj).set_default(...)
> This could also make it possible to use decorators on assignments.
We already can:
result = decorator(obj)
is equivalent to:
@decorator
def obj(): ...
or
@decorator
class obj: ...
except that we can use the decorator on anything we like, not just a
function or class.
[...]
> # This will call the decorator passing in 200 as the object, as
> # well as info.name as the variable being assigned.
> @expose
> SCRIPT_CONSTANT = 200
That would require a change to syntax, and would have to be a separate
discussion.
If there were a way to get the left hand side of assignments as a
parameter, that feature would be *far* to useful to waste on just
decorators. For instance, we could finally do something about:
name = namedtuple("name", fields)
> The two potential benefits I see from this are:
>
> 1. The runtime can pass certain information to the decorator, some
> information in all contexts, and some information in specific contexts
> such as when decorating a class member, decorating a function defined
> within another function, etc
>
> 2. It would be possible to decorate values directly, as the runtime can
> pass relevant information such as the variables name
No, that would require a second, independent change.
We could, if desired, allow decorator syntax like this:
@decorate
value = 1
but it seems pretty pointless since that's the same as:
value = decorator(1)
The reason we have @decorator syntax is not to be a second way to call
functions, using two lines instead of a single expression, but to avoid
having to repeat the name of the function three times:
# Repeat the function name three times:
def function():
...
function = decorate(function)
# Versus only once:
@decorate
def function():
...
--
Steve
More information about the Python-ideas
mailing list