关于python:默认可变参数的惯用方法

Idiomatic way to default mutable arguments

在python中,如果直接将可变类型设为默认参数,则会出现众所周知的边缘情况:

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

通常的解决方法是将参数默认为None,然后将其设置在正文中。但是,有3种不同的方法可以做到这一点,其中2种基本上是相同的,但第三种方法是完全不同的。

1
2
3
4
def foo(x=None):
    if x is None:
        x = []
    return x

这是我通常看到的。

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

语义相同。一行较短,但有些人抱怨python的三元是不自然的,因为它不是从条件开始的,并且建议避免它。

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

这是最短的。我今天才知道这种疯狂。我知道Lisp,所以这对我来说可能比一些Python程序员更不令人惊讶,但我从未想过这会在Python中起作用。此行为不同;如果传递的内容不是None,但计算结果为false(如False),则不会覆盖默认值。如果默认值不为false,则不能使用它,因此如果您有一个非空列表或dict默认值,则不能使用它。但根据我的经验,空名单/口述是99%的相关案例。

有没有想过哪一个最像Python?我意识到这里有一种观点,但我希望有人能给出一个很好的例子或理由来说明什么是最惯用的。与大多数社区相比,python倾向于强烈地鼓励人们以某种方式做事,所以我希望这个问题及其答案会有用,即使它不是完全黑白相间的。


我同意1,因为它比较简单;它的"else"分支是隐含的。很难曲解。

在这种情况下,我不同意第3种说法:bool(x)None[]{}()0和其他一些东西同样是错误的。如果我错误地将一个0传递到一个期望列表的函数中,那么最好是该函数快速失败,而不是将零误认为空列表!

在其他情况下,c and x else y可以是一个方便的三元运算符,但您必须控制c的类型;当它是局部变量而不是函数参数时更容易控制。

如果你经常发现自己用一个值来代替None,那么用一个函数来代替它。考虑一下像x = replace_none(x, [])这样的问题。


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

1
2
3
4
def foo(x=None):
    if x is None:
        x = []
    return x

x or []技巧只能在以下情况下使用:

  • 参数不会发生变化(因为您将空集合替换为新集合)。
  • 你不在乎论点的不同错误价值(因为你失去了[]{}None、my-special-class-with-__bool__之间的任何差异)。
  • 你认为任何阅读你的代码的人都知道它是如何工作的(或者想弄清楚它)。

作为旁注,如果默认值为真,则可以使用or技巧:如果x不可靠,x or [1]仍将是[1]。但是你不能用[]作为论据,因为它将被[1]取代。