为什么“可变默认参数修复”语法如此丑陋,问python新手

27 投票
8 回答
2773 浏览
提问于 2025-04-15 21:36

接下来是我一系列“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 个回答

5

这个函数的使用场景非常特殊,它允许你可选地传入一个列表来修改,但如果你不传入列表,它会生成一个新的列表。为了这个特殊的情况,真的没必要使用特别的语法。认真想想,如果你多次调用这个函数,为什么要把第一次调用(只传一个参数)和后面的调用(需要两个参数来继续修改已有的列表)区分开来呢?比如,假设有一个叫 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)

总是使用两个参数,这样可以避免重复,并且逻辑会简单得多。

引入特殊的语法会鼓励和支持这种特殊的使用情况,但其实没有必要去鼓励和支持这样一个极其奇特的用法——现有的、完全正常的语法对于这种极少见的用法来说已经足够好了;-).

11

这被称为“可变默认值陷阱”。你可以查看这个链接了解更多:http://www.ferg.org/projects/python_gotchas.html#contents_item_6

简单来说,a_list 在程序第一次运行时就被初始化了,而不是每次你调用这个函数时(这可能是你从其他语言的经验中得出的想法)。所以每次你调用这个函数时,你并不是在得到一个新的列表,而是在重复使用同一个列表。

我想问题的答案是,如果你想往列表里添加东西,直接去做就行,不用特意创建一个函数来处理。

这样做:

>>> my_list = []
>>> my_list.append(1)

比这样做更清晰、更容易阅读:

>>> my_list = my_append(1)

在实际情况下,如果你需要这种行为,可能会创建一个自己的类,里面有管理内部列表的方法。

6

默认参数是在定义函数时就被计算的,这种做法通常是比较合理的,因为这正是我们常常想要的。如果不是这样的话,当环境稍微变化时,可能会导致一些让人困惑的结果。

用一种神奇的 local 方法来区分这些参数并不是一个理想的选择。Python 试图让事情变得简单明了,目前没有明显的替代方案可以取代现有的写法,而不去破坏 Python 一直以来的一致性。

撰写回答