为了账号安全,请及时绑定邮箱和手机立即绑定

如何在Python中的非默认参数之前声明一个带有默认参数的函数?

如何在Python中的非默认参数之前声明一个带有默认参数的函数?

慕森卡 2024-01-27 15:02:27
我想声明一个带有非默认(强制)参数的函数,前面有一个默认参数,就像内置 range 函数一样:range(start, end, step)有办法做到这一点吗?当我尝试以这种方式定义一个函数时,我得到了SyntaxError一个def example(a = 0, b)
查看完整描述

3 回答

?
吃鸡游戏

TA贡献1829条经验 获得超7个赞

在 Python 中,位置参数必须位于关键字参数之前——这只是该语言的规则。也就是说,有几种方法(我不一定推荐)可以编写一个函数来解决这个问题。


这本质上就是内置range函数执行您所描述的操作的方式:


def range(*args):

    # default values

    start = 0

    step = 1


    n_args = len(args)

    if not 0 < n_args < 4:

        raise TypeError(f'range expected 1 arguments, got {n_args}')

    elif n_args == 1:

        stop = args[0]

    elif n_args == 2:

        start, stop = args

    else:

        start, stop, step = args

    

    # etc...

它要求参数以特定顺序传递,然后根据传递的参数数量确定要为哪些变量赋值。


或者,考虑以下事项:


def example(a=10, b=None):

    assert b is not None, "argument 'b' is required"

    # etc...

这是一个更丑陋的解决方案,因为:

  1. 您必须b作为关键字参数传递,而不是位置参数

  2. 函数签名不会向用户提供任何b所需的指示 - 它看起来像是可选的,但如果未显式传递,该函数将会失败。

  3. 您必须将参数的“无效”值设置为您 100% 确定没有人愿意实际传递给该参数的值。

除非有真正令人信服的理由不这样做,否则最好坚持通常的格式(首先是位置参数,可选地随后收集 leftover *args,然后是关键字参数,可选地随后收集 leftover **kwargs)。这是人们期望使用函数的方式,并使您的代码更易于使用和阅读。如果确实有令人信服的理由偏离这一点,请模仿 的设置range

编辑:

回复:您关于“更正”函数签名以显示某些参数名称而不是 的问题*args,上面的示例极大地简化了range内置函数的工作方式。实际上,它是用 C 实现的,其签名/文档字符串在此处设置。它更类似于定义在__call__构造函数内运行的方法的类,而不是常规函数。

手动覆盖对象的签名会很棘手(更不用说避免使用“正常”参数顺序的大量额外步骤)——最好将“正确”参数名称放入文档字符串中。

有一些非常非常古怪的方法可以做到这一点。例如,您可以将函数包装在一个装饰器中,该装饰器使用类似inspect.Signature修改函数对象的东西。__init_subclass__或者,也许您可以将其转换为一个类,让它从某个父类继承,然后在父类和__new__子类中使用组合来拦截和修改传递给构造函数的参数。但这些解决方案还远远不够,值得一问的是为什么没有更简单的方法来解决这个问题。在我看来,这是因为这根本不是你想做的事情。


查看完整回答
反对 回复 2024-01-27
?
互换的青春

TA贡献1797条经验 获得超6个赞

你不能。默认参数必须位于参数列表的最后。如果您想要不同的行为,那么您必须在函数内部进行验证。


def primes_in_interval(start=2, end=None):

    assert(end is not None)

    # Rest of code.


primes_in_interval(end=10)

您可以执行类似的操作来获取您正在寻找的调用约定。


def primes_in_interval(end_or_start, end=None):

    if end is None:

        start = 2

        end   = end_or_start

    else:

        start = end_or_start

        end   = end

    # Rest of code.


primes_in_interval(10)      # Start will be 2 and end 10

primes_in_interval(8, 16)   # Start will be 8 and end 16


查看完整回答
反对 回复 2024-01-27
?
缥缈止盈

TA贡献2041条经验 获得超4个赞

你不能。range定义参数的方式是内置range的,并且使用不同的解析参数的方式,大致相当于接收*args, **kwargs和手动解析它们(它手动拒绝非空kwargs,但仍然必须接受它们,因为CPython 的 C API 的约定)。如果你想自己做,也可以,但 Python 没有帮助:


def myrange(*args):

    if not args or len(args) > 3:

        raise TypeError("Expected 1-3 args")

    if len(args) == 1:

        start, step = 0, 1

        stop, = args

    else:

        start, stop, step = (args + (1,))[:3]  # Stupid trick to reduce number of cases


    # Actual work done here


查看完整回答
反对 回复 2024-01-27
  • 3 回答
  • 0 关注
  • 104 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信