任何人只要长时间摆弄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?)
编辑:
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解释器的内部工作一无所知(我也不是编译器和解释器的专家),所以如果我提出任何不合理或不可能的建议,不要怪我。
假设python对象是可变的,我认为在设计默认参数时应该考虑到这一点。实例化列表时:
a = []
你希望得到一个新的列表。
为什么a=[]
def x(a=[]):
在函数定义而不是调用上实例化新列表?这就像你在问“如果用户不提供参数,那么实例化一个新列表,并将其作为调用者生成的列表使用”。我认为这是模棱两可的:
def x(a=datetime.datetime.now()):
用户,是否希望a默认为定义或执行x时对应的日期时间?在本例中,与前一例一样,我将保持与默认参数“赋值”是函数的第一条指令(函数调用时调用datetime.now())相同的行为。另一方面,如果用户想要定义时间映射,他可以写:
b = datetime.datetime.now()
def x(a=b):
我知道,我知道:这是一个结束。或者Python可以提供一个关键字来强制定义时间绑定:
def x(static a=b):
这是一种性能优化。由于此功能,您认为这两个函数调用中哪一个更快?
def print_tuple(some_tuple=(1,2,3)):
print some_tuple
print_tuple() #1
print_tuple((1,2,3)) #2
我会给你一个提示。这是拆卸(参见http://docs.python.org/library/dis.html):
#1
0 LOAD_GLOBAL 0 (print_tuple)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
#2
0 LOAD_GLOBAL 0 (print_tuple)
3 LOAD_CONST 4 ((1, 2, 3))
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
我怀疑有经验的行为是否有实际用途(谁真的在C中使用了静态变量,而没有滋生bug?)
正如您所看到的,使用不可变的默认参数会带来性能上的好处。如果它是一个频繁调用的函数,或者默认参数需要很长时间才能构造,那么这可能会有所不同。此外,请记住Python不是C。在C中,您可以使用非常免费的常量。在Python中,你没有这个好处。