为什么“可变默认参数修复”语法如此丑陋,问python新手
接下来是我一系列“Python新手问题”的内容,基于另一个问题。
前言
你可以去这个链接,然后往下滚动到“默认参数值”那一节。在那里你会找到以下内容:
def bad_append(new_item, a_list=[]):
a_list.append(new_item)
return a_list
def good_append(new_item, a_list=None):
if a_list is None:
a_list = []
a_list.append(new_item)
return a_list
在python.org上还有一个“重要警告”,里面用的也是这个例子,不过并没有说它“更好”。
一种说法
所以,这里问的问题是:为什么在一个提倡“优雅语法”和“易用性”的编程语言中,“好的”语法却在一个已知问题上显得如此丑陋呢?
编辑:
另一种说法
我并不是在问为什么或者怎么会这样(谢谢Mark提供的链接)。
我想问的是为什么语言中没有更简单的内置替代方案。
我觉得更好的方法可能是在def
内部做一些事情,这样名字参数就可以附加到一个“局部”或“新的”可变对象上。类似于:
def better_append(new_item, a_list=immutable([])):
a_list.append(new_item)
return a_list
我相信有人能想出更好的语法,但我也猜测一定有很好的理由解释为什么没有这样做。
8 个回答
这个函数的使用场景非常特殊,它允许你可选地传入一个列表来修改,但如果你不传入列表,它会生成一个新的列表。为了这个特殊的情况,真的没必要使用特别的语法。认真想想,如果你多次调用这个函数,为什么要把第一次调用(只传一个参数)和后面的调用(需要两个参数来继续修改已有的列表)区分开来呢?比如,假设有一个叫 betterappend
的函数,它能做一些有用的事情,因为在当前的例子中,直接用 .append
就好了!
def thecaller(n):
if fee(0):
newlist = betterappend(foo())
else:
newlist = betterappend(fie())
for x in range(1, n):
if fee(x):
betterappend(foo(), newlist)
else:
betterappend(fie(), newlist)
这简直是疯狂,显然应该是这样:
def thecaller(n):
newlist = []
for x in range(n):
if fee(x):
betterappend(foo(), newlist)
else:
betterappend(fie(), newlist)
总是使用两个参数,这样可以避免重复,并且逻辑会简单得多。
引入特殊的语法会鼓励和支持这种特殊的使用情况,但其实没有必要去鼓励和支持这样一个极其奇特的用法——现有的、完全正常的语法对于这种极少见的好用法来说已经足够好了;-).
这被称为“可变默认值陷阱”。你可以查看这个链接了解更多:http://www.ferg.org/projects/python_gotchas.html#contents_item_6
简单来说,a_list
在程序第一次运行时就被初始化了,而不是每次你调用这个函数时(这可能是你从其他语言的经验中得出的想法)。所以每次你调用这个函数时,你并不是在得到一个新的列表,而是在重复使用同一个列表。
我想问题的答案是,如果你想往列表里添加东西,直接去做就行,不用特意创建一个函数来处理。
这样做:
>>> my_list = []
>>> my_list.append(1)
比这样做更清晰、更容易阅读:
>>> my_list = my_append(1)
在实际情况下,如果你需要这种行为,可能会创建一个自己的类,里面有管理内部列表的方法。
默认参数是在定义函数时就被计算的,这种做法通常是比较合理的,因为这正是我们常常想要的。如果不是这样的话,当环境稍微变化时,可能会导致一些让人困惑的结果。
用一种神奇的 local
方法来区分这些参数并不是一个理想的选择。Python 试图让事情变得简单明了,目前没有明显的替代方案可以取代现有的写法,而不去破坏 Python 一直以来的一致性。