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

时间:2010-04-14 18:17:36

标签: python mutable names

现在关注my series of "python newbie questions"并基于another question

特权

转到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

甚至还有一个"Important warning" on python.org这个例子,但并不是说它“更好”。

一种方法

所以,这里的问题是:为什么“好”语法比已知问题丑陋,就像promotes "elegant syntax" and "easy-to-use"编程语言中那样?

编辑:

另一种说法

我不是在问 why or how it happens (感谢Mark提供链接)。

我问为什么没有更简单的替代内置语言

我认为更好的方法可能是在def本身做一些事情,其中​​name参数将附加到def中的“local”或“new”,可变对象。类似的东西:

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

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

8 个答案:

答案 0 :(得分: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)

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

答案 1 :(得分:6)

def语句执行时评估默认参数,这可能是最合理的方法:它通常是想要的。如果不是这种情况,当环境稍有变化时,可能会导致令人困惑的结果。

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

答案 2 :(得分: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)

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

介绍特殊情况语法鼓励和支持特殊用例,并且在鼓励和支持这个非常特殊的用例方面真的没有多大意义 - 现有的,完全规则的语法就好了对于用例非常罕见的使用; - )。

答案 3 :(得分: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分配给该新字符串。默认参数可以反复使用,因为它没有改变,你在开始时复制了它。

答案 4 :(得分:2)

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

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

可能会奏效,但很快就会很快变老。很快,“if a is None:a = CorrectObject()”模式成为第二天性,你不会觉得它很难看 - 你会发现它很有启发性。

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

答案 5 :(得分:1)

可能你不应该将这两个功能定义为好的和坏的。 您可以使用带有列表或词典的第一个来实现相应对象的就地修改。 如果您不知道可变对象的工作原理,但是如果您知道自己在做什么,这种方法可能会让您感到头疼。我认为这是正常的。

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

答案 6 :(得分:0)

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

答案 7 :(得分:0)

这比good_append(),IMO:

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

您还可以格外小心并检查a_list是否为列表...