Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Please explain why these two builtin functions behave different when passed in keyword arguments

Tags:

python

Consider these different behaviour::

>> def minus(a, b):
>>    return a - b

>> minus(**dict(b=2, a=1))
-1

>> int(**dict(base=2, x='100'))
4

>> import operator
>> operator.sub.__doc__
'sub(a, b) -- Same as a - b.'
>> operator.sub(**dict(b=2, a=1))
TypeError: sub() takes no keyword arguments

Why does operator.sub behave differently from int(x, [base]) ?

like image 481
canadadry Avatar asked Jun 05 '12 08:06

canadadry


2 Answers

It is an implementation detail. The Python C API to retrieve arguments separates between positional and keyword arguments. Positional arguments do not even have a name internally.

The code used to retrieve the arguments of the operator.add functions (and similar ones like sub) is this:

PyArg_UnpackTuple(a,#OP,2,2,&a1,&a2)

As you can see, it does not contain any argument name. The whole code related to operator.add is:

#define spam2(OP,AOP) static PyObject *OP(PyObject *s, PyObject *a) { \
  PyObject *a1, *a2; \
  if(! PyArg_UnpackTuple(a,#OP,2,2,&a1,&a2)) return NULL; \
  return AOP(a1,a2); }

spam2(op_add           , PyNumber_Add)
#define spam2(OP,ALTOP,DOC) {#OP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)}, \
                           {#ALTOP, op_##OP, METH_VARARGS, PyDoc_STR(DOC)},
spam2(add,__add__, "add(a, b) -- Same as a + b.")

As you can see, the only place where a and b are used is in the docstring. The method definition also does not use the METH_KEYWORDS flag which would be necessary for the method to accept keyword arguments.

Generally spoken, you can safely assume that a python-based function where you know an argument name will always accept keyword arguments (of course someone could do nasty stuff with *args unpacking but creating a function doc where the arguments look normal) while C functions may or may not accept keyword arguments. Chances are good that functions with more than a few arguments or optional arguments accept keyword arguments for the later/optional ones. But you pretty much have to test it.

You can find a discussion about supporting keyword arguments everywhere on the python-ideas mailinglist. There is also a statement from Guido van Rossum (the Benevolent Dictator For Life aka the creator of Python) on it:

Hm. I think for many (most?) 1-arg and selected 2-arg functions (and rarely 3+-arg functions) this would reduce readability, as the example of ord(char=x) showed.

I would actually like to see a syntactic feature to state that an argument cannot be given as a keyword argument (just as we already added syntax to state that it must be a keyword).

One area where I think adding keyword args is outright wrong: Methods of built-in types or ABCs and that are overridable. E.g. consider the pop() method on dict. Since the argument name is currently undocumented, if someone subclasses dict and overrides this method, or if they create another mutable mapping class that tries to emulate dict using duck typing, it doesn't matter what the argument name is -- all the callers (expecting a dict, a dict subclass, or a dict-like duck) will be using positional arguments in the call. But if we were to document the argument names for pop(), and users started to use these, then most dict sublcasses and ducks would suddenly be broken (except if by luck they happened to pick the same name).

like image 80
ThiefMaster Avatar answered Oct 18 '22 19:10

ThiefMaster


operator is a C module, which defines functions differently. Unless the function declaration in the module initialization includes METH_KEYWORDS, the function will not accept keyword arguments under any conditions and you get the error given in the question.

like image 4
Ignacio Vazquez-Abrams Avatar answered Oct 18 '22 17:10

Ignacio Vazquez-Abrams