默认可变参数的惯用方式

5

在Python中,如果你直接将可变类型作为默认参数,那么就会出现众所周知的边际情况:

def foo(x=[]): return x
y = foo()
y.append(1)
print foo()

通常的解决方法是将参数默认值设置为None,然后在函数体中进行设置。但是,有3种不同的方法来实现这一点,其中2种基本相同,但第三种方法则完全不同。
def foo(x=None):
    if x is None:
        x = []
    return x

这是我通常看到的。

def foo(x=None):
    x = [] if x is None else x
    return x

在语义上完全相同。有一行代码更短,但有些人抱怨Python的三元运算符不太自然,因为它没有以条件开头,建议避免使用。

def foo(x=None):
    x = x or []

这是最简洁的方法。今天我才了解到这种疯狂的方法。我懂Lisp,所以这对我来说可能比一些Python程序员更少令人惊讶,但我从未想过这在Python中会奏效。这种行为有所不同;如果你传递的值不是None,但求值结果为False(例如False),它将不会覆盖默认值。如果默认值不会求值结果为false,它无法使用,因此如果你有一个非空列表或字典作为默认值,它就不能使用。但在我的经验中,空列表/字典是感兴趣的情况的99%。

关于哪种方法最符合Python风格,有何想法?我知道这其中有一些主观因素,但我希望有人能给出一个好的例子或理由,说明哪种方法被认为是最惯用的。与大多数社区不同,Python通常强烈鼓励人们按照某种方式做事情,因此我希望这个问题及其答案即使不是完全黑白分明,也会有所裨益。


通常在Python中使用x = A或B来创建三元语句被认为是不好的做法...尽管如此,你可以这样做...但我会 主张 Python三元语句(选项B)是最具Python风格的...但我很确定这个问题将会被关闭,因为它并不适合在SO上进行讨论(SO讨厌“意见”,更喜欢“事实”)。 - Joran Beasley
1
但是第三个 id 是什么? - Carcigenicate
我还有一个给你:def foo(**kwargs): return kwargs.get('x', []) - marcelm
@marcelm 我不明白你所说的“another”是什么意思。你是指另一个边缘情况,还是另一种解决问题的方法?我认为是后者。但是,**kwargs对调用者的行为完全不同,它允许传递随机参数。显然,除非你要转发到另一个函数或确实打算使用所有参数,否则不应使用**kwargs。感谢你提醒我明确地传递x=[],我没有想到这一点。 - Nir Friedman
如果你愿意采用第一个版本的单行版本,即 if x is None: x = [],那么第二个版本甚至不会比它“短一行”。 - Peter Hansen
显示剩余4条评论
2个回答

2
我会选择#1,因为它更简单;它的“else”分支是隐含的。这样更难误解。
在这种情况下,我不会选择#3:bool(x)对于None[]{}()0和其他一些东西同样为false。如果我错误地将0传递给期望列表的函数,最好让函数快速失败,而不是将零误认为是空列表!
在其他情况下,c and x else y可以是一个方便的三元运算符,但你必须控制c的类型;当它是一个局部变量而不是函数参数时,这样做会更容易。
如果你经常发现自己为None替换一个值,请为此编写一个函数。考虑类似x = replace_none(x, [])的东西。

1

我认为在一般情况下第一种方式最好。第一种和第二种方式在功能上是等效的,但对于新手来说第一种更容易阅读。

def foo(x=None):
    if x is None:
        x = []
    return x

"x or [] 技巧只能在以下情况下使用:"
"
  • 参数不会被改变(因为你将空集合替换为新的集合)。
  • 您不关心参数的不同 falsy 值之间的区别(因为您失去了 []{}None、my-special-class-with-__bool__ 之间的任何差异)。
  • 您认为任何阅读您代码的人都知道它的工作原理(或者想要去找出来)。
"
"

顺便提一下,如果默认值为 true,则可以使用 or 技巧:x or [1] 如果 x 是 falsy,仍将是 [1]。但是您无法使用 [] 作为参数,因为它将被替换为 [1]

"

如果默认值为真,可以使用“or”技巧:如果x为假,x or [1]仍然是[1]。当你将[]作为参数传递时,这种方法会失效,因为x会被错误地更改为[1]。我想这就是OP的意思。 - flornquake
@flornquake 是的,你说得对,也许我应该把它拼出来。 - Nir Friedman
我认为这与默认值为 true 无关;x or []x or [1] 都会将 0 替换为列表,这或许同样不理想。但现在我明白了你的意图。 - Mark
@Mark,从某种意义上说,这与Python通常期望特定接口有关。通常情况下,将整数传递给列表是不好的。因此,如果默认值是字典,我们可以限制自己只使用类似于字典的类型,实际上它们的行为非常类似于字典(例如默认和有序字典)。但是,在Python中,大多数真实类型只有一个假值。如果这是您的默认值,则在传递正确类型时不会出现问题。但是,如果True值(例如非空字典)是默认值,则无法显式传递空字典,这是一个问题。 - Nir Friedman
好的,很公平。我稍微更新了最后一部分,随意进行进一步编辑。 - Mark

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