[Python-ideas] Python Decorator Improvement Idea

Eric V. Smith eric at trueblade.com
Sat Jun 16 20:51:35 EDT 2018


On 6/16/2018 8:22 PM, Michael Selik wrote:
> The idea of having a dunder to introspect the bound variable name has 
> been discussed before. You can find the past discussions in the mailing 
> list archive. If I recall correctly, there were very few use cases 
> beyond namedtuple. With dataclasses available in 3.7, there may be even 
> less interest than before.

One such thread is here:
https://mail.python.org/pipermail/python-ideas/2011-March/009250.html

Eric

> 
> 
> On Sat, Jun 16, 2018, 9:04 AM Brian Allen Vanderburg II via Python-ideas 
> <python-ideas at python.org <mailto:python-ideas at python.org>> wrote:
> 
> 
>     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 <http://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 <http://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.
> 
> 
>     _______________________________________________
>     Python-ideas mailing list
>     Python-ideas at python.org <mailto:Python-ideas at python.org>
>     https://mail.python.org/mailman/listinfo/python-ideas
>     Code of Conduct: http://python.org/psf/codeofconduct/
> 
> 
> 
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
> 


More information about the Python-ideas mailing list