Вопрос

In Python, is it safe to give keyword arguments that are not Python identifiers to a function? Here is an example:

>>> '{x-y}'.format(**{'x-y': 3})  # The keyword argument is *not* a valid Python identifier
'3'
>>> '{x-y}'.format(x-y=3)
  File "<ipython-input-12-722afdf7cfa3>", line 1
SyntaxError: keyword can't be an expression

I am asking this because it is more convenient for me to format with names that contain a dash (because the values correspond to command-line argument with dashes in their name). But is this behavior reliable (i.e. can it vary between version of Python)?

I am not sure that using non-identifiers as keyword arguments is officially supported: in fact, the documentation reads:

If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments.

… where "keyword arguments" are defined as having a name which is an identifier:

keyword_arguments ::= keyword_item ("," keyword_item)*

keyword_item ::= identifier "=" expression

where identifiers are restricted in what characters they can use (- is for instance forbidden):

identifier ::= (letter|"_") (letter | digit | "_")*

So, the documentation indicates that the mapping given to ** in a function call should only contain valid identifiers as keys, but CPython 2.7 accepts more general keys (for format() and functions with a ** argument, which do not put values in variables). Is this a reliable feature?

Это было полезно?

Решение

First of all: the **{...} call convention with non-identifier names only works if the called function has a **kw argument to receive them, as it too cannot define explicit keyword arguments that are not valid identifiers.

I'd say that the keyword_arguments grammar only applies to the parser of the source code, and cannot ever be seen as a functional restriction on the contents of the **expression result. The functional description below does not restrict the keys of the dictionary explicitly, nor does the function definition documentation.

Instead, since the grammar allows an expression, and the functional spec states that that should resolve to a mapping the contents of which are treated as additional keyword arguments, it is clear (to me) that there are no restrictions on the keys at all, beyond the normal ones applicable to Python dictionaries (keys must be immutable). You can pass in tuple or numeric keys for all Python cares. The functional spec states how the contents are treated, not that the contents must fit a certain format.

So, in my opinion the functional spec would have to explicitly restrict the keys in the **expression dictionary to disallow what you are doing, because the grammar certainly does not. Changing that would be a huge backwards-incompatible change, and is not likely to ever be added.

Note that even though the spec doesn't mention any restrictions on the keys of the dictionary, CPython does:

>>> def f(*args, **kw): print args, kw
... 
>>> f(**{1: 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() keywords must be strings

This is a restriction made by the python interpreter when invoking code objects (user defined functions). The reason why is explained in the source code right after the part that raises the above exception:

/* Speed hack: do raw pointer compares. As names are
   normally interned this should almost always hit. */

By restricting keywords to strings a speed optimisation is possible.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top