I made a mistake. This sentence: My idea is to optionally allow any callable object to write a > __decoration_call__ method that gets called in lieu of the __call__ method > when the callable object is employed using decorator syntax. When this > happens, the decorated named is supplied- not counting self- as the first > argument (e.g., by_name), which contains the str value of the name the > decorator was applied to.
Should read: My idea is to optionally allow any callable object to write a __decoration_call__ method that gets called in lieu of the __call__ method when the callable object is employed using decorator syntax. When this happens, the decorated named is supplied- not counting self- as the *SECOND* argument (e.g., by_name), which contains the str value of the name the decorator was applied to. --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler On Wed, May 26, 2021 at 12:43 PM Ricky Teachey <ri...@teachey.org> wrote: > On Wed, May 26, 2021 at 7:54 AM Steven D'Aprano <st...@pearwood.info> > wrote: > >> On Wed, May 26, 2021 at 01:33:07PM +0200, Stéfane Fermigier wrote: >> >> > Last point that I like in the decorator syntax: it's >> > >> > I can compose N different decorators and keep the intent obvious: >> > >> > @acl(READ, WRITE) >> > @constraint(10 < _ < 100) >> > @not_null >> > @indexed >> > @depends_on(whatever) >> > @inject >> > @... >> > first_name: str >> >> Hmm, that's a good point. >> >> On the other hand, it would be terribly confusing if the same syntax: >> >> @decorate >> >> had radically different meaning depending on whether it was followed by >> a class/function or a bare name. >> >> >> -- >> Steve >> > > ....and previously Steve also said: > > On Tue, May 25, 2021 at 3:28 AM Steven D'Aprano <st...@pearwood.info> > wrote: > >> On Mon, May 24, 2021 at 06:36:47PM -0700, micro codery wrote: >> >> > Basically this would add syntax to python that would transform >> > @decorator("spam this") variable >> > into >> > variable = decorator("variable", "spam this") >> >> That is confusingly different from decorator syntax in other contexts.... >> >> @decorator("spam this") >> def func(): pass >> >> # transformed to: >> >> def func(): pass >> func = decorator("spam this")(func) >> >> ...the critical difference is that the argument "spam this" should be >> passed to the decorator *factory*, which then returns the actual >> decorator that gets applied to the variable. (Or function/ class in the >> case of regulator decorator syntax.) > > > Well, in actuality even if it were implemented the way you described, it > is still going to be very different--- I might even say radically different. > > These two ideas of a decorator syntax result are not the same: > > RESULT A: function decorator > # func = decorator("spam")(func) > > RESULT B: variable decorator > # name = decorator("spam")("name") > > ...because func is passed as an object, but "name" a string representing > the name of the object. Two very different things. > > For this reason I think I would agree even more so that the differences in > the decorator behavior would be an extremely significant point of confusion. > > This got me to thinking: what if access to the variable name were provided > by another means, and ONLY when the decorator syntax is employed? > > So then I started to write this big long email and it kind of got out of > hand. Hopefully it isn't a disaster. > > FIRST LAYER: RICK'S (LIKELY HALF-BAKED) COUNTER PROPOSAL > > Maybe employment of decorator syntax could OPTIONALLY trigger a new dunder > method-- here I'll just call it __decoration_call__-- with the signature: > > def __decoration_call__(self, obj: Any, by_name: str) -> Any: ... > > Before I describe what I intend by this, first I will stipulated that what > I am proposing here should only be implemented so that the behavior of all > currently existing decorators (i.e., all callables) would remain exactly as > it does today. > > So, for any existing callable with the name decorator, this: > > @decorator("spam this") > def func(): ... > > ...continues to mean this, just as it does today: > > def func(): ... > func = decorator("spam this")(func) > > My idea is to optionally allow any callable object to write a > __decoration_call__ method that gets called in lieu of the __call__ method > when the callable object is employed using decorator syntax. When this > happens, the decorated named is supplied- not counting self- as the first > argument (e.g., by_name), which contains the str value of the name the > decorator was applied to. > > Let's explain using code examples. > > In actuality, unless I'm wrong (I might be; not an expert) current > decorator syntax is really sugar for: > > def func(): ... > func = decorator.__call__("spam this").__call__(func) > > My proposal is to make it such that: > > @decorator > def func(): ... > > ...*can result* in this: > > def func(): ... > func = decorator.__decoration_call__( func, "func") > > And also so that this: > > @decorator("spam this") > def func(): ... > > ...*can result* in this: > > def func(): ... > func = decorator.__call__("spam this").__decoration_call__(func, "func") > > I say "*can result*" because this has the following limitations: > > 1. occurs only when the callable object is employed as a decorator > 2. occurs only when __decoration_call__ exists (and if it doesn't, > just revert back to the usual __call__(func_object, obj)) > > Here is an example to further illustrate the suggested behavior. It is not > intended to demonstrate a useful example. You could write a callable object > like this: > > class Decorator: > def __call__(self, obj): > print("spam") > return obj > def __decoration_call__(self , obj, by_name): > print("eggs") > print(by_name) > return obj > > decorator = Decorator() > > And the behavior would be like this: > > def func(): ... > func = decorator(func) > # Console output: > # 'spam' > > @decorator > def func(): ... > # Console output: > # 'eggs' > # 'func' > > In order to preserve current behavior, the built-in <class 'function'> > type would not grow its own new __decoration_call__ method. So in the > case of existing normal functions, and also in the case of existing custom > Callable objects, the fallback would be to just use __call__ as normal. > > In this way, all of the proposal is optional. But as illustrated above, > the new dunder can be implemented by any customized Callable class. > > I think __decoration_call__ by itself could provide a lot of interesting > possibilities for existing class and function decoration. But my main > motivation for this is as a different way of implementing the variable > decorator idea proposed by Jeremiah. > > SECOND LAYER: VARIABLE DECORATORS > > With __decoration_call__ in place, we could do a lot of interesting things > if we take the additional step of broadening existing decorator syntax such > that you can decorate not just functions and classes but also variables > (similar the variable decorator proposal recently proposed by Jeremiah). > But first let's define the variable decorator syntax behavior. > > Using the same decorator object I have defined above, and borrowing > Jeremiah's original example, the new syntax would transform this: > > @decorator variable > > ...into this: > > variable = decorator.__decoration_call__(None, "variable") > > EXPLANATION OF None: in the example above there is no assignment on the > decorated line, and so there is no object to pass to the dunder at that > point . Because of this, the first argument, which is usually the object > being decorated, is supplied as None in the call to __decoration_call__ . > Only the name being assigned to the as-yet uncreated object exists. > > And the new syntax would transform this: > > @decorator variable = "spam" > > ...into this: > > variable = decorator.__decoration_call__(variable, "variable") > > With the behavior of this variable decoration defined as shown above, here > are some of the interesting possibilities I was considering. > > One possibility is avoiding the pitfalls of name repetition in the RAD > framework context previously mentioned by Stéfane Fermigier in Jeremiah's > variable decorator thread (see his post for examples). It could see other > frameworks, like web frameworks, making use if this too. > > (Here I will repeat some of the possibilities suggested by Jeremiah in his > first post) The standard library and other library functions could, as > needed, make use of __decoration_call__ as they see fit. I could see > factories like namedtuple, make_dataclass, several typing factories, and > Enum, implementing __decoration_call__ so you can say things like: > > @namedtuple Point = "x y z" > @make_dataclass Point = [("x", int), ("y", int), ("z", int)] > @typing.TypeVar T = (str, bytes) > @typing.NewType UserId = int > @enum.Enum Colors = "RED GREEN BLUE" > > Admittedly, these examples are not all that compelling on their own, but I > think they read really well and they would allow you to avoid retyping a > name (and potentially making a mistake in retyping it), so I think I really > like it. > ___ > SIDEBAR > > I also note that the above examples use a different idiom than suggested > by Jeremiah. He has suggested these: > > @namedtuple("x y z") Point > @make_dataclass([("x", int), ("y", int), ("z", int)]) Point > @typing.TypeVar( (str, bytes) ) T > @typing.NewType(int) UserId > @enum.Enum("RED GREEN BLUE") Colors > > ...but using my proposal I do not see a way to make this idiom easily work > with the existing factories. I am not sure which of these two idioms people > would prefer. I can see pros and cons for both idioms. If people preferred > the second idiom, new decorator objects could be added for it. > ___ > > One interesting possibility might be to modify inspect.getsource so that > it can retrieve the RHS of the line on which it appears*: > > @inspect.getsource my_var = 1/2 > print(my_var) > # Result > # '@inspect.getsource my_var = 1/2' > > * NOTE: I am admittedly hand-waving this. Maybe there are reasons > getsource would not be able to do this? > > Using the rhs string from this new version of getsource, it would be a > pretty simple matter for a symbolic math library like sympy to replace the > float value resulting from 1/2 in my_var with a symbolic math value: > > @sympy.sympify my_var = 1/2 > # Result is not 0.5, but: > # 1/2 > > ...or create a Fraction (using a modified Fraction type): > > @Fraction my_var = 1/2 > # Result is not 0.5, but: > # Fraction(1, 2) > > ..or a Decimal (using a modified Decimal type): > > @Decimal my_var = 0.5 > # Result is not 0.5, but: > # Decimal('0.5') > > MAYBE A DEFAULT DUNDER AFTER ALL...? > > One final possibility: I have said above we would not supply any default > __decoration_call__ method, and instead fallback on __call__. But it might > be beneficial to supply a default __decoration_call__ with the following > implementation: > > def __decoration_call__(self, func, by_name): > if func is None: > return self(by_name) > return self(func) > > This would allow things like this, "out of the box": > > @typing.NewType Color = str > @Color GREEN > > ...which becomes: > > Color = typing.NewType.__decoration_call__(str, "Color") > GREEN = Color.__decoration_call__(None, "GREEN") > > In the above, Color is just a stand-in for str. If there is a default > __decoration_call__ method as I wrote above, then: > > Color.__decoration_call__(None, "GREEN") > > ...is actually just: > > str.__decoration_call__(None, "GREEN") > > ..and the default implementation just returns: > > str("GREEN") > > ...and assigns it to the name, GREEN. > > Guess I'll leave it at that. Let's see how this is received. > > --- > Ricky. > > "I've never met a Kentucky man who wasn't either thinking about going home > or actually going home." - Happy Chandler > >
_______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/Z73G5T4VQ2HEHSHRELILXS3HUYYOM2SS/ Code of Conduct: http://python.org/psf/codeofconduct/