Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is is safe to use a function accepts kwargs keyword arguments that are not identifiers?

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?

like image 747
Eric O Lebigot Avatar asked Jun 06 '13 07:06

Eric O Lebigot


People also ask

What is Kwargs argument?

**kwargs stands for keyword arguments. The only difference from args is that it uses keywords and returns the values in the form of a dictionary.

Is Kwargs a keyword?

In this article, we learned about two special keywords in Python – *args and **kwargs . These make a Python function flexible so it can accept a variable number of arguments and keyword arguments, respectively.

How do you beat Kwargs argument?

Inside the function, the kwargs argument is a dictionary that contains all keyword arguments as its name-value pairs. Precede double stars ( ** ) to a dictionary argument to pass it to **kwargs parameter. Always place the **kwargs parameter at the end of the parameter list, or you'll get an error.

Why do we use args and Kwargs?

We use *args and **kwargs as an argument when we are unsure about the number of arguments to pass in the functions.


1 Answers

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.

like image 106
Martijn Pieters Avatar answered Sep 18 '22 16:09

Martijn Pieters