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