非标准可选参数默认值

4

我有两个函数:

def f(a,b,c=g(b)):
    blabla

def g(n):
    blabla

c是函数f中的可选参数。如果用户没有指定它的值,程序应该计算g(b),这将是c的值。但代码无法编译 - 它说名称“b”未定义。如何修复?

有人建议:


def g(b):
    blabla

def f(a,b,c=None):
    if c is None:
        c = g(b)
    blabla

但这并不起作用。也许用户想让c为None,然后c将有另一个值。

2
None是Python对象。 None不是字符串'None'。您询问是否未填写c(表示None)才能为其设置g(b)。给出的答案完全正确。因为用户指定None的唯一方法是不提供第三个参数,这就是您的问题。 - user136565
1
什么意思?我不能将None作为参数传递吗?对象的缺失并不等同于将其设置为None。例如:type()会出错,但是type(None) == 'NoneType'。 - ooboo
5个回答

28
def f(a,b,c=None):
    if c is None:
        c = g(b)
如果c可以是有效值的 None ,那么你需要这样做:
sentinel = object()
def f(a,b,c=sentinel):
    if c is sentinel:
        c = g(b)

抱歉,这不是一个好的解决方案。也许用户想要 c 值为 None?然后函数 g(b) 会将 c 设置为其他值,尽管这不是用户的意图。 - ooboo
4
那你就麻烦了:http://docs.python.org/reference/compound_stmts.html#function-definitions 你应该重新考虑你的设计或者阅读一本好的Python书籍。 - Boldewyn
我来举个例子。假设b是一个大数,g(b)计算它的因数。进一步假设,为了方便起见,我们不将数字本身和1视为因数,因此质数的分解实际上评估为None。c是用户可选的参数,用于指定是否已知因式分解。那么为什么要调用可能非常昂贵的g(b)呢? - ooboo
你是完全正确的,由于None的例子,我把它搞混了。最初,我只是想让c成为列表b中特定元素的索引。如果用户知道它,计算它也没有意义。 - ooboo
@ooboo:所以如果调用者提供了因数分解,c将是一个列表,对吗?一个由除1和b之外的因子组成的列表?那么让他们传递[]表示“我知道因数分解;没有非1非b因子”,或者传递None表示“因数分解未知”。如果您不喜欢None,请使用具有有意义名称的哨兵,例如FactorsUnknown。 - John Machin
显示剩余4条评论

3

你不能用那种方式操作。

在函数内部,检查是否指定了c。如果没有,则进行计算。

def f(a,b,c=None):
    if c == None:
        c = g(b)
    blabla

3
c is None 的速度比 c == None 更快。 - Paolo Bergantino
2
“is None”比“== None”更快这是事实,但“is None”更受青睐的更重要的原因是它更加健壮。等式运算符可以被重载,使得不是None的对象仍然可以与None相等。在comp.lang.python上,对于None的适当测试一直存在争议。除非你真的知道自己在做什么,否则请使用“is”。 - John Y
嘿,Paolo。你能解释一下为什么 c is None 比 c == None 更快吗?这与对象有关还是在比较数字参数时也应该使用“is”?谢谢! - ooboo
@ooboo:绝对不应该使用“is”来比较数字!“is”的作用是检查两个名称是否引用同一个对象。实际上,它是一个指针比较。这使得它快速,但对于数字和字符串来说不合适,在Python中,它们可以有多个副本(即具有相同值的不同对象实例)。 - John Y

2

c的值(g(b))将在编译时评估。因此,您需要先定义g,然后才能定义f。当然,在那个阶段还需要定义全局变量b

b = 4

def g(a):
    return a+1

def test(a, c=g(b)):
    print(c)

test(b)

打印出5。


它强调的是问题不在于将函数作为默认值,而在于该函数的 b 未定义。 - luc
这里b是一个全局变量...在我的例子中它不是。 - ooboo
2
那么它就不会起作用了!在编译时,b需要存在,当评估g(b)时! - SilentGhost

1

关于问题

sentinel = object()
def f(a, b, c=sentinel):
  if c is sentinel:
    c = g(b)

这意味着,除非该代码是函数/方法的一部分,否则sentinel是全局/公共的。因此,某些人仍然可以调用f(23, 42, sentinel)。但是,如果f是全局/公共的,您可以使用闭包将sentinel变为本地/私有,以便调用者无法使用它:

def f():
  sentinel = object()
  def tmp(a, b, c=sentinel):
    if c is sentinel:
      c = g(b)
  return tmp
f = f()

如果您担心静态代码分析器可能会对f产生错误的理解,那么您可以为工厂使用相同的参数:

def f(a, b, c=object()): #@UnusedVariable
  sentinel = object()
  def tmp(a, b, c=sentinel):
    if c is sentinel:
      c = g(b)
  return tmp
f = f(23, 42)

0
def f(a,b,*args):
    if len(args) == 1:
        c = args[0]
    elif len(args) == 0:
        c = g(b)
    else:
        raise Exception('Function takes 2 or 3 parameters only.')
    blabla

def g(n):
    blabla

你可能可以更好地组织它,但这是主要想法。或者你可以使用**kwargs并像f(a,b,c=Something)这样使用函数,只需相应地修改f即可。

文档


SilentGhost已经修复了它,感谢他的贡献。抱歉我匆忙赶去吃午饭,所以代码有点丑陋并且有错误。但这是一个可行的解决方案,而Paolo Bergantino的解决方案如果我想将“sentinel”作为参数c传递,就会出现问题,因为“sentinel”现在是命名空间中的额外变量。 - Maiku Mori
1
@Maiku Mon:sentinel 的整个意义在于它不是 c 的普通值,而是一个特殊的值,表示“请为我计算 c = g(b);你不希望将其作为 c 的普通值传递”。 - John Machin
@John,问题可以这样解决。OP已经预先计算了c。因此,如果他将其传递给f,他不希望再次计算它。如果他没有传递它,则需要计算它。 - SilentGhost
@John,我明白它的工作原理。我的论点的第一部分可能是无意义的,因为很有可能没有人会将“sentinel”用作参数,但我仍然不喜欢它。这只是我的偏见。 - Maiku Mori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接