On Tue, May 25, 2021 at 5:29 PM Steven D'Aprano email@example.com 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.
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')`
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 = @@ # mylist = 'mylist'
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'). 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.
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.
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.