长时间修改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中使用了静态变量,却没有滋生bug?)
编辑:
巴切克举了一个有趣的例子。连同您的大多数评论,特别是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
从绑定的一部分(该行的函数对象)将在定义时发生,部分(默认参数的分配)将在函数调用时发生。
实际行为更加一致:执行该行时将评估该行的所有内容,即在函数定义时进行评估。
TLDR:定义时间默认值是一致的,并且更具表现力。
定义一个函数影响两个范围:该范围定义包含的功能,并执行范围由包含的功能。尽管很清楚块是如何映射到作用域的,但问题是在哪里
def <name>(<args=defaults>):
属于:该
def name
零件必须在定义范围内进行评估-name
毕竟我们希望在那里可用。仅在内部评估函数将使其无法访问。由于
parameter
是一个常量名,因此我们可以与同时“评估”它def name
。这还有一个优势,那就是它可以生成具有已知签名的功能name(parameter=...):
,而不是裸露的签名name(...):
。现在,什么时候评估
default
?一致性已经说了“在定义时”:
def <name>(<args=defaults>):
在定义时最好也评估其他所有内容。延迟其中的一部分将是令人惊讶的选择。两种选择都不相等:如果
default
在定义时求值,它仍然会影响执行时间。如果default
在执行时评估,则不会影响定义时间。选择“在定义时”允许表达两种情况,而选择“在执行时”只能表达一种情况: