I’ve never said it in any thread before but the allowance of this syntax literally freaks me out ! as well as
f(a=, b=2)
I mean, allowing this implies to remove a SyntaxError check, and in the middle of a long code, if this line was meant to assign the value 1 to a but it is missing, I’m pretty sure a human would not spot this from a rush of the eyes, and neither the linter, neither the interpreter traceback would help finding the bug. This is the kind of thing (like a trailing comma) that makes you lose a day or two of work time because of one mistyped characters.
Yes. I think every assignment operators (=, :, whatever else…) should have both a left-hand and a right-hand side on solid ground. This is one fundamental protection from silent bugs.
Both variants seem quite ok to me. {a:, 'b': 2} is probably a bit riskier, but {:a, 'b': 2} looks quite solid to me.
There already is a case of similar pattern in f'{a=}'. I don’t think I have ever made a mistake there or had any bugs with it. And I am using f-strings not only for logging but for logic as well (I know many would advise me not to, but I am happy with it - it is fast, short and does everything well).
Could you give an example case of high risk silent bug that {:a, 'b': 2} would introduce?
I actually managed to control my fear and went comfortable using it, it is not an assignment btw.
{'b':2, ***(a)} feels much safer to me, and foremost, does not imply to turn off syntax error policy for an accidental deletion of any left-hand-side of :.
→ which lights up another question : should the two following lines be equivalent ? (I think yes)
Not sure this perfectly answers the question yet it is the main point I think :
def connect_db(host, port, user, password) : ...
# Now we convert this function arguments to self-named ones
def connect_db(host, :port, :user, :password) : ... # one error here, would you spot it in the middle of 100 lines ???
def connect_db(***(host, port, user, password)) : ... # global refactoring is easier this way
True, this only addresses construction of dict and not the other aspect of this proposal.
In essence I think there are 2 things that this does:
It validates that correct values of dictionary are assigned to correct variables
It ensures that variable names are the same as dictionary keys from which values are assigned
As of (1), tuple can often be used instead. Or:
c, t, m = [(c := connect_db())[k] for k in ['connection', 'terminal', 'metadata']]
# Or even neater
c, t, m = itemgetter('connection', 'terminal', 'metadata')(connect_db())
As of (2), I am not certain whether this is a good idea as this syntax is limited to use-cases where local namespace is free of variables named as those keys.
And to avoid it, I need to either:
a) not use proposed syntax.
b) change the name of some other variable
(b) violates “there should be one and only one…” in a fairly awkward manner.
As for for (a), very often this can be undesirable as it would break long working variable naming schemes or I am not the owner of the code and it is required that I make as little change as possible.
After encountering 2 such cases where I can not do (b) and need to think about different construct, I would probably stop using this all together.
It might be possible that this works even if connect_db does not return a dict, simply the names in the calling namespace will be checked against the ones in the function space…
Possibly that triple star unpacking could have several uses :
Would be broken if metadata was added to return dictionary. And this is kind of one of main points of using dict as return value - return additions do not break existing use cases.
Otherwise, if this aspect is not needed, dict is an overkill and tuple would be more appropriate.
I think this sort of feature could be separate problem from dict creation. Maybe could be part of “typing” stuff, where user could optionally opt in to match not only return types, but also return variable names.