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.