我们如何在调用函数时强制命名参数?

2025-03-18 08:54:00
admin
原创
47
摘要:问题描述:在 Python 中你可能有一个函数定义:def info(obj, spacing=10, collapse=1) 可以通过以下任一方式调用:info(odbchelper) info(odbchelper, 12) info...

问题描述:

在 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 插件?

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


解决方案 1:

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

来自文档:

” 或 “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):

完成示例:

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'

因此,您被迫始终给出值。如果您不调用它,则不必执行任何其他名为参数强制的操作。

解决方案 2:

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

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

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

def foo(**kwargs):
    pass

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

解决方案 3:

确实,大多数编程语言都将参数顺序作为函数调用契约的一部分,但这并不是必须的。为什么会这样?那么,我对这个问题的理解是,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在正确位置使用 sentinel 对象切换到位置模式。是的,这也可以看作是一个优点。

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

我发现,当我敞开心扉思考时,任何问题或他人的决定都不会显得愚蠢、无知或可笑。恰恰相反:我通常能学到很多东西。

解决方案 4:

你可以通过一种在 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")

这将允许:

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)智能编辑器并丢失签名信息。您的社会契约是通过强制(其中一些)要求关键字来提供某些信息,而这些关键字的呈现顺序已变得无关紧要。

解决方案 5:

python3 的关键字参数 ( *) 可以在 python2.x 中使用以下方法模拟**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'

这可以使用下面的方法进行模拟,注意,我已经擅自切换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)

和行为:

>>> 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,则应避免使用

解决方案 6:

更新:

我意识到使用**kwargs并不能解决问题。如果你的程序员随意更改函数参数,那么你可以将函数更改为:

def info(foo, **kwargs):

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

这实际上取决于布莱恩说了什么。


(...)人们可能会在spacing和之间添加参数collapse(...)

一般来说,当改变函数时,新的参数应该总是放在最后。否则会破坏代码。这应该是显而易见的。

如果有人改变函数导致代码破坏,那么这种改变必须被拒绝。

(正如 Bryan 所说,这就像一份合同)

(...) 有时并不总是清楚需要输入什么。

通过查看函数的签名(即def info(object, spacing=10, collapse=1)),可以立即看到每个没有默认值的参数都是必需的。参数的

用途应该放入文档字符串中。


旧答案(保留完整性)

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

您可以按如下方式定义函数:

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是一个包含任意关键字参数的字典。您可以检查是否存在强制参数,如果不存在,则引发异常。

缺点是,哪些参数是可能的可能不再那么明显,但有了适当的文档字符串,就应该没问题。

解决方案 7:

正如其他答案所说,更改函数签名不是一个好主意。要么在末尾添加新参数,要么在插入参数时修复每个调用者。

如果您仍想这样做,请使用函数装饰器和inspect.getargspec函数。它的用法如下:

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

的实现require_named_args留给读者作为练习。

我不会费心。每次调用该函数时都会很慢,如果能更仔细地编写代码,您将获得更好的结果。

解决方案 8:

您可以将函数声明为**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.

解决方案 9:

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

在 python 中如果使用 *args 意味着您可以为此参数传递 n 个位置参数 - 这些参数将作为函数内部的元组进行访问。

如果使用 kw ,那意味着它的关键字参数,可以作为字典访问 - 您可以传递 n 个 kw 参数,如果您想限制用户必须按顺序输入序列和参数,那么不要使用 * 和 - (这是为大型架构提供通用解决方案的 Python 方式......)

如果你想用默认值来限制你的函数,那么你可以在它里面检查

def info(object, spacing, collapse)
  spacing = 10 if spacing is None else spacing
  collapse = 1 if collapse is None else collapse

解决方案 10:

您可以使用**运算符:

def info(**kwargs):

这样人们就被迫使用命名参数。

解决方案 11:

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

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

如果您确实希望顺序重要,那么改变它就别指望任何事情都能起作用!

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用