如何强制在调用函数时指定参数名称?

204 投票
11 回答
77386 浏览
提问于 2025-04-15 23:30

在Python中,你可以定义一个函数:

def info(obj, spacing=10, collapse=1)

这个函数可以通过以下几种方式调用:

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

这是因为Python允许你以任何顺序传递参数,只要你给它们起了名字。

我们现在遇到的问题是,随着一些较大的函数不断增加,可能会有人在spacingcollapse之间添加新的参数,这样就可能导致错误的值被传递给那些没有名字的参数。此外,有时候也不太清楚应该传入什么。

我们该如何强制要求大家给某些参数命名呢?这不仅仅是一个编码标准,理想情况下还希望有一个标志或者pydev插件来实现这个功能。

这样在上面提到的四个例子中,只有最后一个会通过检查,因为所有的参数都有名字。

11 个回答

14

没错,大多数编程语言在调用函数时会把参数的顺序当作一种规则,但其实这并不是必须的。为什么要这样呢?我理解这个问题的意思是,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)

调用者如果想要通过位置来提供参数spacingcollapse(而不出现错误),唯一的方法是:

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实现了类似但非常优雅的语言支持机制,这在其他答案中有描述。

我发现,当我敞开心扉去思考时,没有任何问题或别人的决定显得愚蠢、傻或只是可笑。相反,我通常会学到很多东西。

63

你可以通过以下方式强制在Python3中使用关键字参数来定义一个函数。

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

通过把第一个参数设为没有名字的位置参数,你就迫使每个调用这个函数的人都必须使用关键字参数。我想这就是你想问的内容。在Python2中,唯一能做到这一点的方法是这样定义一个函数:

def foo(**kwargs):
    pass

这样会强制调用者使用关键字参数,但这并不是一个很好的解决方案,因为你还需要添加检查,只接受你需要的参数。

371

在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'

所以你必须总是提供一个值。如果你不调用它,就不需要做其他任何强制性的命名参数。

撰写回答