
I'm still digesting this proposal (though I think I like it quite a bit), but there's one thing in particular that just really doesn't gel with me. Is there any particular reason why you've proposed assignment decorators as being on the same line as the assignment statement rather than on the preceding line(s)? I like this: @typing.TypeVar T = str, bytes about a million times better than: @typing.TypeVar T = str, bytes Because the latter feels (to me) too similar for comfort to this: int foo = 3 Which is in my mind not very pythonic. Also, my brain just has an easier time parsing the multiline version than the single-line one (though I concede that a combination of familiarity and syntax highlighting would solve that issue eventually). It also represents an asymmetry between the syntax of the proposed assignment decorators and the syntax for function and class decorators. And finally, it doesn't cleanly accommodate use-cases like those proposed by Stéfane in the previous thread: @acl(READ, WRITE) @constraint(10 < _ < 100) @not_null @indexed @depends_on(whatever) @inject first_name: str Whereas the multiline variant does. Another question I've got is whether you've got any solution in mind for more complex assignment cases? After thinking about the proposals in the Steven's thread (the googly-eye symbol), I think I'm a fan of providing the name as a *tuple of tuple of strings*, like so: @decorate foo = first, _, third, *rest = bar = [0, 1, 2, 3, 4] such that __decoration_call__ receives the following tuple for its second argument: (('foo',), ('first', '_', 'third', '*rest'), ('bar',) And it can then choose to do whatever it likes with all the available names without having to implement its own logic for parsing the names back out from a string. My only other question would be, what would you think of adding a third argument to pass the type hint along? Function decorators can always access the __annotations__ on the function object they act on, but currently your __decoration_call__ can't capture type information. With such an argument, this: @decorate foo: int = 3 Would be provided to 'decorate' as: def __decoration_call__(self, obj, names, annotation): print(obj) # 3 print(names) # (('foo',),) print(annotation) # int But yeah, overall I'm liking this proposal best out of the ones that have been discussed recently. The google-eyes symbol is a solid idea, and is sufficient for many basic use-cases, but it's not really as powerful as real decorators and cannot accommodate the more complex cases (like the example above with several chained decorators, some of which are actually decorator factories). On Wed, May 26, 2021 at 5:52 PM Ricky Teachey <ricky@teachey.org> wrote:
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 <ricky@teachey.org> wrote:
On Wed, May 26, 2021 at 7:54 AM Steven D'Aprano <steve@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 <steve@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-leave@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/Z73G5T... Code of Conduct: http://python.org/psf/codeofconduct/