
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.
This is sounding promising. I'm liking this.
Then:
RED = @@ # transforms to `RED = 'RED'` GREEN = "dark " + @@.lower() # transforms to `GREEN = "dark " + 'GREEN'.lower()` myclass = type(@@, bases, namespace) # transforms to `myclass = type('myclass', bases, namespace)` # Not all functions expect the name as first argument. result = function(arg, value, @@) # transforms to `result = function(arg, value, 'result')`
Decent.
If there's no target, it resolves to None:
print(@@) # print(None)
or if people prefer a SyntaxError, I'm okay with that too.
Bikesheddable. I'd be inclined to go with SyntaxError, and make @@ a syntactic construct that is inherently part of the assignment; that way, there's no confusion in other contexts. No big deal either way.
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.
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.
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...
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 = @@
Target resolution is performed at compile-time, not runtime. There's no global variable called "@@". That means that this won't work:
code = compile("type(@@, bases, namespaces)", '', 'single') # above transforms to `type(None, bases, namespace)` myclass = eval(code)
But I think that restriction is fine.
Absolutely.
This has the same sort of value as the C preprocessor stringification operator. It's incredibly handy in making self-referential statements. 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.
Questions:
1) Is this restricted to the "=" assignment operator, or will other operators trigger this too? x += f(@@) # ? if x := f(@@): # ?
2) What about other forms of assignment? for spam in foo(@@): # ? with open(@@ + ".json") as config: # ? from sys import @@ as argv # okay that's just stupid
3) Is this a string literal, or a magic token that happens to evaluate as a string? x = @@ ".json" # Legal if @@ is a string literal
No wrong answers. (Well, unless you say "tomato". That is a very wrong answer to a yes/no question.)
I'm liking this. It might mean that class syntax and decorator abuse become less necessary as ways to get around name duplication.
ChrisA