在Python中强制命名参数

时间:2010-06-03 11:00:54

标签: python function coding-style parameter-passing

在Python中,您可能有一个函数定义:

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

可以通过以下任何一种方式调用:

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

感谢Python允许任意顺序参数,只要它们被命名。

我们遇到的问题是,随着我们的一些较大功能的增长,人们可能会在spacingcollapse之间添加参数,这意味着错误的值可能会转到非{命名。此外,有时候并不总是清楚需要进行什么。我们正在采用一种方法强迫人们命名某些参数 - 不仅仅是编码标准,而且理想情况下是标志或pydev插件?

所以在上面的4个例子中,只有最后一个会通过检查,因为所有参数都被命名。

我们只会针对某些功能启用它,但是有关如何实现这一点的任何建议 - 或者如果可能的话,我们将不胜感激。

11 个答案:

答案 0 :(得分:153)

在Python 3中 - 是的,您可以在参数列表中指定*

来自docs

  

“*”或“* identifier”之后的参数是仅限关键字的参数和   可能只传递使用过的关键字参数。

>>> 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):

答案 1 :(得分:24)

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

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

通过使第一个参数成为一个没有名称的位置参数,你强制所有调用该函数的人都使用关键字参数,这是我认为你所问的问题。在Python2中,唯一的方法是定义一个像这样的函数

def foo(**kwargs):
    pass

这会强制调用者使用kwargs,但这不是一个很好的解决方案,因为你必须检查只接受你需要的参数。

答案 2 :(得分:9)

是的,大多数编程语言使参数顺序成为函数调用契约的一部分,但这不是需要。为什么会这样?我对这个问题的理解是,如果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的知识)。

优点:

  • 参数在数量和含义上是明确的(当然,如果也选择好名称,则后面的参数)。
  • 如果将sentinel指定为第一个参数,则需要通过名称指定所有参数。
  • 调用该函数时,可以使用相应位置的标记对象__named_only_start切换到位置模式。
  • 可以预期比其他替代品更好的性能。

缺点:

  • 在运行时进行检查,而不是编译时。
  • 使用额外参数(尽管不是参数)和附加检查。与常规功能相比性能下降很小。
  • 功能是一种没有语言直接支持的黑客(见下面的注释)。
  • 调用该函数时,可以通过使用正确位置的标记对象__named_only_start切换到位置模式。是的,这也可以看作是专业人士。

请记住,这个答案仅对Python 2有效.Python 3实现了其他答案中描述的类似但非常优雅的语言支持机制。

我发现当我打开思绪并思考它时,没有任何问题或其他决定似乎是愚蠢,愚蠢或者只是愚蠢。恰恰相反:我通常会学到很多东西。

答案 3 :(得分:7)

你可以用在Python 2和Python 3中工作,通过制作一个&#34;伪造的&#34;第一个关键字参数,默认值不会发生&#34;自然&#34;。该关键字参数可以在一个或多个没有值的参数之前:

_dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")

这将允许:

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

但不是:

info(odbchelper, 12)                

如果您将功能更改为:

def info(_kw=_dummy, spacing=10, collapse=1):

然后所有参数都必须包含关键字,而info(odbchelper)将不再有效。

这将允许您在_kw之后的任何位置放置其他关键字参数,而不必强迫您在最后一个条目之后放置它们。这通常是有意义的,例如按逻辑分组或按字母顺序排列关键字有助于维护和开发。

因此无需恢复使用def(**kwargs)并丢失智能编辑器中的签名信息。您的社交合同是通过强制(其中一些)要求关键字来提供某些信息,这些信息的提供顺序已经变得无关紧要。

答案 4 :(得分:2)

<强>更新

我意识到使用**kwargs无法解决问题。如果程序员按照自己的意愿更改函数参数,可以将函数更改为:

def info(foo, **kwargs):

并且旧代码会再次中断(因为现在每个函数调用都必须包含第一个参数)。

这真的归结为布莱恩所说的。


  

(...)人们可能会在spacingcollapse之间添加参数(...)

通常,在更改函数时,新参数应始终结束。否则会破坏代码。应该是显而易见的 如果有人更改了该功能以使代码中断,则必须拒绝此更改 (正如布莱恩所说,这就像一份合同)

  

(...)有时候并不总是清楚需要了解什么。

通过查看函数的签名(即def info(object, spacing=10, collapse=1)),应立即看到每个默认值的参数都是必需的。
该参数的 应该进入docstring。


旧答案(保持完整性)

<击> 这可能不是一个好的解决方案:

您可以这样定义功能:

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargs是一个包含任何关键字参数的字典。您可以检查是否存在强制参数,如果不存在,则引发异常。

缺点是,它可能不再那么明显,哪些参数是可能的,但是如果有正确的文档字符串,它应该没问题。

<击>

答案 5 :(得分:1)

您可以将您的功能声明为仅接收**args。这将强制关键字参数,但你需要做一些额外的工作,以确保只传入有效的名称。

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.

答案 6 :(得分:1)

可以使用*在python2.x中模拟仅python3关键字参数(**kwargs

考虑以下python3代码:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

及其行为:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

这可以使用以下方法模拟,请注意,在“必填命名实参”的情况下,我可以自由地将TypeError切换为KeyError,这样做不会做太多工作同样的异常类型

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

行为:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

该食谱在python3.x中同样有效,但是如果您仅使用python3.x,则应避免使用该食谱

答案 7 :(得分:0)

您可以使用**运算符:

def info(**kwargs):

这样人们就不得不使用命名参数。

答案 8 :(得分:0)

def cheeseshop(kind, *arguments, **keywords):

在python中如果使用* args意味着你可以为这个参数传递n个参数 - 这将是一个列表内的函数来访问

如果使用** kw表示其关键字参数,可以作为dict访问 - 您可以传递n个数量的kw args,如果要限制该用户必须按顺序输入序列和参数,那么请使用*和** - (它的pythonic方式为大型架构提供通用解决方案......)

如果你想用默认值限制你的功能,那么你可以在里面检查

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1

答案 9 :(得分:0)

正如其他答案所说,改变功能签名是一个坏主意。可以在末尾添加新参数,也可以在插入参数时修复每个调用者。

如果您仍想这样做,请使用function decoratorinspect.getargspec功能。它会用到这样的东西:

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

require_named_args的实施留给读者练习。

我不会打扰。每次调用函数时都会很慢,并且通过更仔细地编写代码可以获得更好的结果。

答案 10 :(得分:-2)

我不明白为什么程序员首先会在两个其他人之间添加一个参数。

如果您希望函数参数与名称一起使用(例如info(spacing=15, object=odbchelper)),那么定义它们的顺序无关紧要,因此您最好将新参数放在最后。

如果您确实希望订单变得重要,那么如果您更改订单,则无法期待任何工作!