
On Tue, May 25, 2021 at 10:10:12PM +1000, Chris Angelico wrote:
On Tue, May 25, 2021 at 5:29 PM Steven D'Aprano <steve@pearwood.info> wrote:
Here's a counter-proposal: we have a special symbol which is transformed at compile-time to the left hand assignment target as a string. Let's say we make that special expression `@@` or the googly-eyes symbol.
[...]
Targets aren't limited to a single bare name.
spam.eggs = @@ # spam.eggs = 'spam.eggs'
mylist[2] = @@ # mylist[2] = 'mylist[2]'
What about:
mylist[ 2 ] = @@
? I'm inclined to say that the assignment target is reconstructed from the AST, in order to make it consistent (so this would also assign the string 'mylist[2]'). But, again, bikesheddable.
Let the implementation decide whether it is easier to get the target from the source code or the AST. I don't care either way.
If the key or index is not known at compile-time, it is a syntax error:
mylist[getindex()] = @@ # SyntaxError
Also bikesheddable; I'd actually say that this should assign the string 'mylist[getindex()]', regardless of the value returned by getindex.
My reasoning it that it is safer and more conservative to start with a restriction and relax it later, than to start with a less restrictive version and regret it. But I could be persuaded otherwise :-)
Chained assignments transform to a tuple of target names:
spam = eggs = cheese = func(arg, @@) # spam = eggs = cheese = func(arg, ('spam', 'eggs', 'cheese'))
Hmm. Everything else gives you a single string, this one doesn't. I'd actually be inclined to switch around this one and the next one...
A complication I just thought of is that you can have chained assignment within a sequence unpacking assignment: spam, eggs, aardvark = foo = bar, hovercraft = 'abcd' and the other way around: spam = eggs = (aardvark, foo, bar) = hovercraft = 'abc' That's going to make things tricky.
Sequence unpacking assignment gets transformed as a single comma-seperated string:
spam.eggs, foo, *bar = func(arg, @@) # spam.eggs, foo, *bar = func(arg, ('spam.eggs,foo,*bar'))
... so that assigning the same thing to multiple names gives you a space-separated string (or equals-separated, "spam=eggs=cheese"), but unpacking gives you a tuple of targets, since it then nicely parallels the result it's expecting from the function. That would mean that:
# This assigns a single string to them all eg "spam eggs cheese" spam = eggs = cheese = @@ # This assigns a string to each one: spam, eggs, cheese = @@ # and is equivalent to: spam = @@; eggs = @@; cheese = @@
I have to think about that some more :-)
This has the same sort of value as the C preprocessor stringification operator. It's incredibly handy in making self-referential statements.
Nice analogy.
Python has places where that happens by magic (class and function definitions), but if you want to create your own function that gets the same benefit.... well.... there's __set_name__ if your thing gets put into a class, but otherwise you have to repeat the name.
Yes.
Questions:
1) Is this restricted to the "=" assignment operator, or will other operators trigger this too? x += f(@@) # ? if x := f(@@): # ?
I hadn't thought that far ahead. I did think that we ought to exclude the walrus operator because it would be ambiguous: spam = eggs * (cheese:=foo+bar(@@)) Does @@ get the value 'cheese' or 'spam'? If we require an assignment statement, then it can only be 'spam' and the ambiguity is gone.
2) What about other forms of assignment? for spam in foo(@@): # ?
YAGNI. We can always extend the functionality later. Let's keep it simple: it works for assignment statements, not every binding operation.
3) Is this a string literal, or a magic token that happens to evaluate as a string?
An actual string.
x = @@ ".json" # Legal if @@ is a string literal
Heh, I wouldn't necessarily require that. (Nor would I object to it.) Implicit string concatenation is a nice feature, but I'm not sure we want to extend it. Its not hard to slot an explicit `+` in there. -- Steve