[Python-ideas] Python Decorator Improvement Idea

Brian Allen Vanderburg II brianvanderburg2 at aim.com
Sat Jun 16 12:07:31 EDT 2018


On 06/16/2018 01:22 AM, Steven D'Aprano wrote:
> 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?

Yes, I meant None instead of Null.

> [...]
>> 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.

That was just an initial idea.  However there would be no reason that the
parameter could not be passed directly.  Actually if creating one decorator
that wraps another decorator, being able to pass the parameter on could
be needed.

Also, the decorator would still work in normal syntax, only with that
parameter
set to None

>> 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__.

This works with functions and classes but not other values that may
not have __name__.
>> 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?

What I mean is used inside the body of a class to decorate a class member:

    class MyClass(object):
        @decorator
        def method(self):
            pass

Using the explicit is better than implicit:

    class MyClass(object):
        @decorator(MyClass, ...)
        def method(self):
            pass

However right now that does not work as MyClass does not exist when the
decorator is called.  I'm not sure how Python works on this under the hood
as it's been a long time since I've looked through the source code.  If
Python
gather's everything under MyClass first before it even begins to create the
MyClass object, then it may not be possible, but if Python has already
created
a class object, and just not yet assigned it to the MyClass name in the
module,
then perhaps there could be some way to pass that class object to the
decorator.

I have seen some examples that decorates the class and members to achieve
something similar

    @outerdecorator
    class MyClass:
        @decorator
        def method(self):
            pass

>
>>     # 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)

Agreed it would be a change in syntax.  Using the decorator syntax i've
mentioned
the name being assigned would be passed to that extra info parameter. 
Python
would treat anything in the form of:

    @decorator
    NAME = (expression)

as a decorator as well:

    _tmp = (expression)
    NAME = decorator(_tmp)

Right now, there's litlte use as it is just as easy to say directly

    NAME = decorator(expression)

With this idea, it could be possible to do something like this:

    def NamedTuple(obj @info):
        return namedtuple(info.name, obj)

    @NamedTuple
    Point3 = ["x", "y", "z"]
>> 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():
>     ...
>

The two main use cases I had of this idea were basically assignment
decorators,
pointless as it can just be name = decorator(value), but my idea was to
pass to
the decorator some metadata such as the name being assigned, and as class
member decorators to receive information of the instance of the class object
the member is being declared under.

A more general idea could be to allow a function call to receive a meta
parameter
that provides some context information of the call.  This parameter is
not part of
a parameter list, but a special __variable__, or perhaps could be
retrieved via a
function call.

Such contexts could be:

1) Assignment (includes decorators since they are just sugar for name =
decorator(name))
The meta attribute assignname would contain the name being assigned to

    def fn(v):
        print(__callinfo__.assignname)
        return v

    # prints X
    X = fn(12)

    # prints MyClass
    @fn
    class MyClass:
        pass

    # Should assignname receive the left-most assignment result or the
rightmost othervar
    # Perhaps assignname could be a tuple of names being assigned to
    result = othervar = fn(12)

    #assignname would be myothervar in this augmented assignment
    result = [myothervar := fn(12)]

    # Should expressions be allowed, or would assignname be None?
    result = 1 + fn(12)

With something like this.

    name = namedtuple("name", ...)

could become:

    def NamedTuple(*args):
        return namedtuple(__callinfo__.assignname, args)

    Point2 = NamedTuple("x", "y")
    Point3 = NamedTuple("x", "y", "z")
    etc

2) Class context. The a classobj parameter could contain the class
object it is called under.
This would be a raw object initially as __init__ would not have been
called, but would allow
the decorator to add attributes to a class

    def fn(v):
        print(__callinfo__.classobj) # classobj is None except when the
function is called in the body of a class declaration
        print(__callinfo__.assignname)
        if __callinfo__.classobj:
            data = vars(__callinfo__.classobj).setdefault("_registry", {})
            data[__callinfo__.assignname] = v
        return v

    class MyClass:
        # print main.MyClass (probably something else since __init__ not
yet calls, may just be a bare class object at that timie)
        # print X
        # sets MyClass._registry["X"]
        X = fn(12)

        # print main.MyClass
        # print method
        # sets MyClass._registry["method"]
        @fn
        def method(self):
            pass
   
    # print None
    # print Y
    Y = fn(12)

In this case it's not longer a decorator idea but more of an idea for a
called function to be able to retrieve certain meta information about
it's call.
In the examples above, I used __callinfo__ with attributes, but direct
names would work the same:

    def fn(v):
        print(__assignname__) # May be None if no assignment/etc if
otherfunc(fn(value))
        print(__classobj__) # Will be None unless fn is called directly
under a class body


There may be other contexts and use cases, and better ways.  Just an idea.


-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180616/d8f60b9f/attachment.sig>


More information about the Python-ideas mailing list