28

现在跟随我的“python新手问题”系列并基于另一个问题

特权

转到http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables并向下滚动到“默认参数值”。在那里您可以找到以下内容:

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,其中 name 参数将附加到def可变对象内的“本地”或“新”。就像是:

def better_append(new_item, a_list=immutable([])):
    a_list.append(new_item)
    return a_list

我确信有人可以提供更好的语法,但我也猜测必须有一个很好的解释来解释为什么没有这样做。

4

8 回答 8

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)

在实际情况下,如果您需要这种行为,您可能会创建自己的类,该类具有管理其内部列表的方法。

于 2010-04-14T18:24:17.527 回答
6

默认参数在def语句执行时被评估,这可能是最合理的方法:它通常是想要的。如果不是这样,当环境发生一点变化时,可能会导致令人困惑的结果。

用魔术local方法或类似的方法进行区分远非理想。Python 试图让事情变得非常简单,并且没有明显、明确的替代当前样板文件,它不会与 Python 当前具有的相当一致的语义相混淆。

于 2010-04-14T18:31:07.613 回答
4

一个函数的极其具体的用例允许您选择性地传递一个列表进行修改,但除非您专门传递一个列表,否则会生成一个新列表,绝对不值得使用特殊情况的语法。说真的,如果您要多次调用此函数,为什么要对系列中的第一个调用进行特殊处理(仅传递一个参数)以将其与其他所有调用区分开来(这将需要两个参数能够不断丰富现有列表)?!例如,考虑类似的事情(当然假设它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)

总是使用两个参数,避免重复,并构建更简单的逻辑。

引入特殊情况语法鼓励和支持特殊情况用例,而鼓励和支持这种极其特殊的用例确实没有多大意义——现有的、完全规则的语法对于用例极其罕见的良好用途来说是很好的;- )。

于 2010-04-14T18:33:39.123 回答
3

我已经编辑了这个答案,以包括来自问题中发布的许多评论的想法。

你举的例子是有缺陷的。它会修改您将其作为副作用传递的列表。如果这就是您希望函数工作的方式,那么使用默认参数是没有意义的。返回更新后的列表也没有任何意义。如果没有默认参数,问题就会消失。

如果意图是返回一个新列表,则需要制作输入列表的副本。Python 更喜欢明确的东西,所以由你来制作副本。

def better_append(new_item, a_list=[]): 
    new_list = list(a_list)
    new_list.append(new_item) 
    return new_list 

对于一些不同的东西,您可以制作一个可以将列表或生成器作为输入的生成器:

def generator_append(new_item, a_list=[]):
    for x in a_list:
        yield x
    yield new_item

我认为您误解了 Python 对可变和不可变默认参数的处理方式不同;这根本不是真的。相反,参数的不变性使您可以以一种微妙的方式更改代码以自动执行正确的操作。以您的示例为例,使其适用于字符串而不是列表:

def string_append(new_item, a_string=''):
    a_string = a_string + new_item
    return a_string

此代码不会更改传递的字符串 - 它不能,因为字符串是不可变的。它创建一个新字符串,并将 a_string 分配给该新字符串。默认参数可以反复使用,因为它不会改变,您在开始时复制了它。

于 2010-04-14T20:39:03.553 回答
2

如果您不是在谈论列表,而是在谈论 AwesomeSets,您刚刚定义的一个类怎么办?你想在每个类中定义“.local”吗?

class Foo(object):
    def get(self):
        return Foo()
    local = property(get)

可能会起作用,但会很快变老,很快。很快,“如果 a 为无:a = CorrectObject()”模式成为第二天性,你不会觉得它丑陋——你会发现它很有启发性。

问题不是语法之一,而是语义之一——默认参数的值是在函数定义时评估的,而不是在函数执行时。

于 2010-04-14T18:31:32.777 回答
1

可能您不应该将这两个函数定义为好和坏。您可以将第一个与列表或字典一起使用来实现相应对象的就地修改。如果您不知道可变对象是如何工作的,但如果您知道自己在做什么,那么这种方法会让您头疼,我认为这是可以的。

因此,您有两种不同的方法来传递提供不同行为的参数。这很好,我不会改变它。

于 2010-04-14T18:29:03.393 回答
0

我认为您将优雅的语法与语法糖混淆了。python 语法清楚地传达了这两种方法,只是碰巧正确的方法看起来不如不正确的方法优雅(就语法行而言)。但由于不正确的方法,很好......不正确,它的优雅是无关紧要的。至于为什么没有实现像你在 better_append 中展示的东西,我猜这There should be one-- and preferably only one --obvious way to do it.胜过优雅的小小收获。

于 2010-04-14T18:42:41.957 回答
0

这比 IMO 的 good_append() 更好:

def ok_append(new_item, a_list=None):
    return a_list.append(new_item) if a_list else [ new_item ]

您也可以格外小心并检查 a_list 是否是一个列表...

于 2010-04-14T19:09:43.493 回答