请解释为什么这两个内置函数在传递关键字参数时行为不同。

7
考虑以下不同的行为:
>> 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

为什么 operator.sub 的行为与 int(x, [base]) 不同?

3
如果人们能够解释为什么这个函数选择不使用关键字参数,那就太好了,我也想知道这个原因:D - jamylak
3个回答

7
这是一项实现细节。Python C API to retrieve arguments 区分位置参数和关键字参数。在内部,位置参数甚至没有名称。
用于检索operator.add函数(以及类似的sub)参数的代码如下:
PyArg_UnpackTuple(a,#OP,2,2,&a1,&a2)

正如您所看到的,它不包含任何参数名称。与operator.add相关的整个代码如下:

#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.")

正如您所看到的,ab仅在文档字符串中使用。方法定义也未使用METH_KEYWORDS标志,该标志对于使方法接受关键字参数是必要的。

一般来说,您可以安全地假设在您知道参数名称的基于Python的函数总是接受关键字参数(当然,有人可能会使用*args解包进行恶意操作,但创建参数看起来正常的函数文档),而C函数可能会或可能不会接受关键字参数。具有多个参数或可选参数的函数很可能为后面/可选参数接受关键字参数。但是您几乎必须测试它。

您可以在python-ideas邮件列表上找到支持关键字参数的讨论。此外,还有一份来自Python创始人(也称为终身慈善独裁者)Guido van Rossum的声明:

我认为对于许多(大部分?)一参数和选定的二参数函数(很少有三个或更多参数的函数),这将降低可读性,就像 ord(char=x) 的示例所示。

实际上,我希望看到一种语法功能,以说明参数不能作为关键字参数给出(就像我们已经添加了语法来说明它必须是一个关键字一样)。

我认为添加关键字参数完全错误的一个领域:内置类型或 ABC 的方法并且是可重载的。例如,考虑 dict 上的 pop() 方法。由于参数名称目前未记录,如果有人子类化 dict 并覆盖此方法,或者如果他们创建另一个可变映射类来尝试使用鸭子类型模拟 dict,无论参数名称是什么都无所谓 - 所有调用者(期望 dict、dict 子类或 dict 类似的鸭子)都将在调用中使用位置参数。但是,如果我们记录 pop() 的参数名称,并且用户开始使用这些名称,则大多数 dict 子类和鸭子将突然断开连接(除非他们碰巧选择了相同的名称)。


除了风格选择以外,我应该指出的是接受关键字参数会增加函数调用的开销,如果用户实际上通过关键字向内置函数传递参数,开销将显著增加。在我的机器上对Python 3.5中int("0", 2)int("0", base=2)进行微基准测试表明,后者运行时间约为前者的2.5倍;花费更多的时间用于参数解析而不是实际工作。更糟糕的是,接受单个参数的函数有一个快速路径,可以避免参数包装,但是转换为关键字意味着参数必须被包装在元组和/或字典中。 - ShadowRanger

4

operator是一个C模块,它使用不同的函数定义方式。除非在模块初始化中的函数声明中包含METH_KEYWORDS,否则该函数不会接受关键字参数,无论什么情况下都会出现问题。


3

minus(**dict(b=2, a=1)) 会展开成 minus(b=2, a=1)。这是因为你的定义有参数名 ab

operator.sub(**dict(b=2, a=1)) 会展开成 operator.sub(b=2, a=1)。但这行不通,因为 sub 不接受关键字参数。


2
我很确定他在问为什么它不接受关键字参数,而你没有回答这个问题。 - jamylak
你是说 operator.sub 没有像 minus 这样命名它的参数吗? - canadadry
@BonAmi:没错。operator.sub的参数是无名的。 - Eric
@Eric,C语言中没有实现int类型吗?我该如何检查? - canadadry
@BonAmi:不确定你能不能做到。inspect.getargspec是我的第一个猜测,但它会在C函数上出错。我很确定int是用C实现的,但具有显式命名参数。 - Eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接