在Python中强制命名参数

Mar*_*ayo 94 python coding-style function parameter-passing

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

def info(object, spacing=10, collapse=1)
Run Code Online (Sandbox Code Playgroud)

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

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)
Run Code Online (Sandbox Code Playgroud)

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

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

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

赔率是我们只会为某些功能打开它,但任何关于如何实现这一点的建议 - 或者如果它甚至可能会被赞赏.

Eli*_*sky 182

在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)
Run Code Online (Sandbox Code Playgroud)

这也可以与**kwargs:

def foo(pos, *, forcenamed, **kwargs):
Run Code Online (Sandbox Code Playgroud)


mar*_*ega 26

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

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass
Run Code Online (Sandbox Code Playgroud)

通过使第一个参数成为没有名称的位置参数,您可以强制调用该函数的每个人使用我认为您所询问的关键字参数.在Python2中,唯一的方法是定义一个这样的函数

def foo(**kwargs):
    pass
Run Code Online (Sandbox Code Playgroud)

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


Mar*_*ssi 10

确实,大多数编程语言都将参数顺序作为函数调用契约的一部分,但这不是必须的.为什么会这样?我对这个问题的理解是,如果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)
Run Code Online (Sandbox Code Playgroud)

调用者能够提供参数spacingcollapse位置(没有例外)的唯一方法是:

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)
Run Code Online (Sandbox Code Playgroud)

在Python中,不使用属于其他模块的私有元素的惯例已经非常基本.与Python本身一样,这种参数约定只是半强制执行.

否则,呼叫需要采用以下形式:

info(arg1, arg2, arg3, spacing=11, collapse=2)
Run Code Online (Sandbox Code Playgroud)

一个电话

info(arg1, arg2, arg3, 11, 2)
Run Code Online (Sandbox Code Playgroud)

将值11分配给参数_p,并由函数的第一条指令提升异常.

特点:

  • 之前_p=__named_only_start的参数在位置上(或通过名称)被接纳.
  • 后面的参数_p=__named_only_start必须仅由名称提供(除非__named_only_start获得并使用有关特殊哨兵对象的知识).

优点:

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

缺点:

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

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

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


Ant*_*hon 7

您可以通过在Python 2和Python 3中工作的方式实现这一点,方法 使用"自然"不会出现的默认值"伪造"第一个关键字参数.该关键字参数前面可以有一个或多个没有值的参数:

_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")
Run Code Online (Sandbox Code Playgroud)

这将允许:

info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)
Run Code Online (Sandbox Code Playgroud)

但不是:

info(odbchelper, 12)                
Run Code Online (Sandbox Code Playgroud)

如果将功能更改为:

def info(_kw=_dummy, spacing=10, collapse=1):
Run Code Online (Sandbox Code Playgroud)

然后所有参数必须有关键字,info(odbchelper)不再有效.

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

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


Ant*_*ile 5

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

考虑以下 python3 代码:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)
Run Code Online (Sandbox Code Playgroud)

及其行为:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

这可以使用以下内容进行模拟,请注意,我已经自由地切换TypeErrorKeyError“必需的命名参数”情况,将其设置为相同的异常类型也不会做太多工作

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)
Run Code Online (Sandbox Code Playgroud)

和行为:

>>> 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
Run Code Online (Sandbox Code Playgroud)

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