如何强制在调用函数时指定参数名称?
在Python中,你可以定义一个函数:
def info(obj, spacing=10, collapse=1)
这个函数可以通过以下几种方式调用:
info(odbchelper)
info(odbchelper, 12)
info(odbchelper, collapse=0)
info(spacing=15, object=odbchelper)
这是因为Python允许你以任何顺序传递参数,只要你给它们起了名字。
我们现在遇到的问题是,随着一些较大的函数不断增加,可能会有人在spacing
和collapse
之间添加新的参数,这样就可能导致错误的值被传递给那些没有名字的参数。此外,有时候也不太清楚应该传入什么。
我们该如何强制要求大家给某些参数命名呢?这不仅仅是一个编码标准,理想情况下还希望有一个标志或者pydev插件来实现这个功能。
这样在上面提到的四个例子中,只有最后一个会通过检查,因为所有的参数都有名字。
11 个回答
没错,大多数编程语言在调用函数时会把参数的顺序当作一种规则,但其实这并不是必须的。为什么要这样呢?我理解这个问题的意思是,Python在这方面和其他编程语言有什么不同。除了其他关于Python 2的好答案外,请考虑以下几点:
__named_only_start = object()
def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
if _p is not __named_only_start:
raise TypeError("info() takes at most 3 positional arguments")
return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)
调用者如果想要通过位置来提供参数spacing
和collapse
(而不出现错误),唯一的方法是:
info(arg1, arg2, arg3, module.__named_only_start, 11, 2)
在Python中,不使用其他模块的私有元素的这个约定是非常基本的。就像Python本身一样,这个参数的约定也只是部分强制执行。
否则,调用的形式需要是:
info(arg1, arg2, arg3, spacing=11, collapse=2)
一个调用
info(arg1, arg2, arg3, 11, 2)
会把值11赋给参数_p
,并且函数的第一条指令会引发一个异常。
特点:
- 在
_p=__named_only_start
之前的参数可以通过位置(或者名字)来提供。 - 在
_p=__named_only_start
之后的参数必须通过名字来提供(除非知道并使用特殊的哨兵对象__named_only_start
)。
优点:
- 参数的数量和含义是明确的(当然,如果选择了好的名字,含义会更清晰)。
- 如果哨兵作为第一个参数指定,那么所有的参数都需要通过名字来指定。
- 在调用函数时,可以通过在相应位置使用哨兵对象
__named_only_start
来切换到位置模式。 - 可以预期性能比其他选择更好。
缺点:
检查发生在运行时,而不是编译时。- 使用了一个额外的参数(虽然不是额外的参数值)和一个额外的检查。与普通函数相比,性能略有下降。
- 这个功能是一种技巧,没有语言的直接支持(见下面的说明)。
- 在调用函数时,可以通过在正确的位置使用哨兵对象
__named_only_start
来切换到位置模式。是的,这也可以被视为一个优点。
请记住,这个答案只适用于Python 2。Python 3实现了类似但非常优雅的语言支持机制,这在其他答案中有描述。
我发现,当我敞开心扉去思考时,没有任何问题或别人的决定显得愚蠢、傻或只是可笑。相反,我通常会学到很多东西。
你可以通过以下方式强制在Python3中使用关键字参数来定义一个函数。
def foo(*, arg0="default0", arg1="default1", arg2="default2"):
pass
通过把第一个参数设为没有名字的位置参数,你就迫使每个调用这个函数的人都必须使用关键字参数。我想这就是你想问的内容。在Python2中,唯一能做到这一点的方法是这样定义一个函数:
def foo(**kwargs):
pass
这样会强制调用者使用关键字参数,但这并不是一个很好的解决方案,因为你还需要添加检查,只接受你需要的参数。
在Python 3中 - 是的,你可以在参数列表中使用*
。
来自文档:
在“*”或“*标识符”之后的参数是仅限关键字的参数,
只能通过关键字参数来传递。
>>> def foo(pos, *, forcenamed):
... print(pos, forcenamed)
...
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)
这也可以和**kwargs
结合使用:
def foo(pos, *, forcenamed, **kwargs):
完整的例子:
def foo(pos, *, forcenamed ):
print(pos, forcenamed)
foo(pos=10, forcenamed=20)
foo(10, forcenamed=20)
# basically you always have to give the value!
foo(10)
输出:
Traceback (most recent call last):
File "/Users/brando/anaconda3/envs/metalearning/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3444, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-12-ab74191b3e9e>", line 7, in <module>
foo(10)
TypeError: foo() missing 1 required keyword-only argument: 'forcenamed'
所以你必须总是提供一个值。如果你不调用它,就不需要做其他任何强制性的命名参数。