Consider the following function :
def foo(**kwargs):
print(kwargs)
>>> foo(er=5)
{'er': 5}
>>> foo(**{"er":5})
{'er': 5}
This is normal behavior. But consider the following :
>>> foo(**{"er ":5})
{'er ': 5}
>>> foo(**{"3":5})
{'3': 5}
That’s weird. But what follows is weirder :
>>> foo(**{3:5})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keywords must be strings
Why does that happen now ?
There is another instance where python itself does a identifier:value association with a dict : the globals() builtin. And it allows for any value, even numerals, as keys in the returned dictionary.
In that case the side-effect of it modifying the global scope is undocumented (afaik, I also heard removing that side-effect was under discussion but I may be wrong), so if we give the user a dictionary we shouldn’t restrain them from doing whatever they want with it. The dict reads a scope, it does not create one (or at least it’s not documented as such).
But in the case of double-stars, it’s different, because passing a double-starred dict creates a scope, and only incidentally stores the remaining identifier:value pairs in a supplementary dict (conventionally named “kwargs”). From that point of view, it makes sense then to forbid passing 3 as a key in a double-starred dict, because 3 is not a valid identifier.
But in that case, if we are doing checks on the keys of a double-starred dict, why aren’t we checking the keys of that double-starred dict ? Why are we letting invalid identifiers such as "3" or "er " or even "arg-tup(fup" pass in the function’s kwargs without raising an exception ? ("class" is another problem)
I think that’s an inconsistency which doesn’t make any sense, and that it should be solved either by allowing any dict (with any key type) to be double-starred, or by disallowing any non-valid-identifier key from double-starred dictionaries, typically by calling the equivalent of assert key.isidentifier().
I would personally prefer the second choice, and the possible next step would be to call (the equivalent of) assert not keyword.iskeyword(key).
Also, just to be clear, allowing the user to do kwargs[3] = 6 inside the function is normal, and is not the subject here.