"最小惊讶"和可变的默认参数

发布时间 2023-10-28 19:17:26作者: 小满独家

内容来自 DOC https://q.houxu6.top/?s="最小惊讶"和可变的默认参数

"最小惊讶"和可变的默认参数

任何长时间使用Python的人都会被以下问题困扰(或者被撕裂):

def foo(a=[]):
    a.append(5)
    return a

Python新手会期望这个没有参数的函数总是返回一个只有一个元素的列表:[5]。结果却大相径庭,非常令人惊讶(对于一个新手来说):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

我的一位经理第一次遇到这个问题,称其为语言的“设计缺陷”。我回答说这种行为有一个基本的解释,如果你不了解内部原理,确实会感到困惑和意外。然而,我无法回答(给自己)以下问题:为什么在函数定义时绑定默认参数,而不是在函数执行时?我怀疑这种经验行为在实际应用中没有实际用途(谁真的在C语言中使用了静态变量,而不产生错误?)

编辑

Baczek提出了一个有趣的例子。结合你的大部分评论,特别是Utaal的评论,我进一步阐述了:

def a():
    print("a executed")
    return []

def b(x=a()):
    x.append(5)
    print(x)

a executed
>>> b()
[5]
>>> b()
[5, 5]

对我来说,这个设计决策似乎与参数的作用域有关:是在函数内部还是与它“一起”?

在函数内部进行绑定意味着在函数调用时,x实际上会被绑定到指定的默认值,而不是在定义时。这似乎是一个深刻的缺陷:def行在某种程度上是“混合”的,因为在定义行执行时,函数对象的绑定(以及默认参数的赋值)会发生。

实际的行为更加一致:在定义行执行时,该行的所有内容都会被计算。


实际上,这不是设计缺陷,也不是因为内部或性能问题。这只是因为 Python 中的函数是一等对象,不仅仅是一段代码。

当你这样想时,就完全明白了:函数是在定义时被求值的对象;默认参数是“成员数据”,因此它们的状态可能会在一次调用到另一次调用之间发生变化——就像在任何其他对象中一样。

无论如何,effbot(Fredrik Lundh)对这种行为的原因有很好的解释,可以参考他在Python中的默认参数值中的文章。

我觉得很清楚,我非常建议阅读它,以便更好地了解函数对象的工作原理。