【问题标题】:What is the pythonic way to avoid default parameters that are empty lists?避免空列表的默认参数的pythonic方法是什么?
【发布时间】:2010-09-26 20:32:40
【问题描述】:

有时,有一个空列表的默认参数似乎很自然。然而Python produces unexpected behavior in these situations

例如,如果我有一个函数:

def my_func(working_list=[]):
    working_list.append("a")
    print(working_list)

第一次调用时,默认值会起作用,但之后的调用会更新现有列表(每次调用一个"a")并打印更新的版本。

那么,获得我想要的行为的 Pythonic 方法是什么(每次调用都有一个新列表)?

【问题讨论】:

  • 同样的行为也发生在集合上,尽管你需要一个稍微复杂一点的例子才能让它显示为一个错误。
  • 由于链接失效,让我明确指出这是所需的行为。默认变量在函数定义时进行评估(第一次调用时),而不是每次调用函数时。因此,如果你改变一个可变的默认参数,任何后续的函数调用都只能使用改变的对象。

标签: python


【解决方案1】:
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    # alternative:
    # working_list = [] if working_list is None else working_list

    working_list.append("a")
    print(working_list)

The docs 表示您应该使用None 作为默认值,并在函数体中明确使用test for it

【讨论】:

  • 最好说:if working_list == None: 还是 if working_list: ??
  • 这是在 python 中的首选方式,即使我不喜欢它,因为它很丑。我想说最好的做法是“如果 working_list 是 None”。
  • 这个例子中的首选方式是说:如果 working_list 是 None 。调用者可能使用了一个带有自定义追加的空列表类对象。
  • Mohit Ranka:注意 not working_list 如果它的长度为 0,则为 True。这会导致不一致的行为:如果函数接收到一个包含某些元素的列表,它的调用者将更新他的列表,如果列表为空,则不会被触摸。
  • @PatrickT 正确的工具取决于具体情况——可变参数函数与采用(可选)列表参数的函数非常不同。您必须在它们之间进行选择的情况比您想象的要少。 Varargs 在 arg 计数发生变化时非常有用,但在编写代码时是固定的。就像你的例子。如果它是运行时变量,或者您想在列表中调用f(),则必须调用f(*l),这很糟糕。更糟糕的是,实施mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen']) 将使用可变参数。如果是def mate(birds=[], bees=[]):,那就更好了。
【解决方案2】:

其他答案已经提供了所要求的直接解决方案,但是,由于这对于新的 Python 程序员来说是一个非常常见的陷阱,因此值得添加解释 Python 为何如此行事的解释,这在 The Hitchhikers Guide to Python 中得到了很好的总结Mutable Default Arguments下:

Python 的默认参数在定义函数时评估一次,而不是每次调用函数时(就像在 Ruby 中一样)。这意味着,如果您使用可变的默认参数并对其进行变异,您并且已经对该对象进行变异,以便将来调用该函数。

【讨论】:

    【解决方案3】:

    在这种情况下并不重要,但您可以使用对象标识来测试无:

    if working_list is None: working_list = []
    

    您还可以利用布尔运算符或在 python 中的定义方式:

    working_list = working_list or []
    

    虽然如果调用者给你一个空列表(算作假)作为 working_list 并期望你的函数修改他给它的列表,这会出现意外行为。

    【讨论】:

    • or 建议看起来不错,但当提供 01TrueFalse 时,它的表现令人惊讶。
    【解决方案4】:

    如果函数的意图是修改作为working_list 传递的参数,请参阅 HenryR 的答案(=None,检查内部是否存在 None)。

    但如果您不打算改变参数,只需将其用作列表的起点,您可以简单地复制它:

    def myFunc(starting_list = []):
        starting_list = list(starting_list)
        starting_list.append("a")
        print starting_list
    

    (或者在这个简单的例子中只是print starting_list + ["a"],但我想这只是一个玩具示例)

    一般来说,改变你的参数在 Python 中是不好的风格。完全期望改变对象的唯一函数是对象的方法。改变一个可选参数就更罕见了——只有在某些调用中才会发生的副作用真的是最好的接口吗?

    • 如果你是按照 C 中“输出参数”的习惯来做的,那是完全没有必要的——你总是可以将多个值作为一个元组返回。

    • 如果您这样做是为了有效地构建一长串结果而不构建中间列表,请考虑将其编写为生成器并在调用它时使用result_list.extend(myFunc())。这样,您的调用约定仍然非常干净。

    一种经常改变可选参数的模式是递归函数中隐藏的“备忘录”参数:

    def depth_first_walk_graph(graph, node, _visited=None):
        if _visited is None:
            _visited = set()  # create memo once in top-level call
    
        if node in _visited:
            return
        _visited.add(node)
        for neighbour in graph[node]:
            depth_first_walk_graph(graph, neighbour, _visited)
    

    【讨论】:

      【解决方案5】:

      我可能跑题了,但请记住,如果您只想传递可变数量的参数,pythonic 方法是传递元组*args 或字典**kargs。这些是可选的,比语法 myFunc([1, 2, 3]) 更好。

      如果你想传递一个元组:

      def myFunc(arg1, *args):
        print args
        w = []
        w += args
        print w
      >>>myFunc(1, 2, 3, 4, 5, 6, 7)
      (2, 3, 4, 5, 6, 7)
      [2, 3, 4, 5, 6, 7]
      

      如果要传递字典:

      def myFunc(arg1, **kargs):
         print kargs
      >>>myFunc(1, option1=2, option2=3)
      {'option2' : 2, 'option1' : 3}
      

      【讨论】:

        【解决方案6】:

        引用https://docs.python.org/3/reference/compound_stmts.html#function-definitions

        执行函数定义时,从左到右计算默认参数值。这意味着表达式在定义函数时被计算一次,并且每次调用都使用相同的“预计算”值。当默认参数是可变对象(例如列表或字典)时,这一点尤其重要:如果函数修改了对象(例如,通过将项目附加到列表中),则默认值实际上已被修改。这通常不是预期的。解决这个问题的一种方法是使用 None 作为默认值,并在函数体中显式测试它,例如:

        def whats_on_the_telly(penguin=None):
            if penguin is None:
                penguin = []
            penguin.append("property of the zoo")
            return penguin
        

        【讨论】:

          【解决方案7】:

          已经提供了好的和正确的答案。我只是想提供另一种语法来编写您想要做的事情,例如,当您想要创建一个具有默认空列表的类时,我发现它更漂亮:

          class Node(object):
              def __init__(self, _id, val, parents=None, children=None):
                  self.id = _id
                  self.val = val
                  self.parents = parents if parents is not None else []
                  self.children = children if children is not None else []
          

          这个 sn-p 使用 if else 运算符语法。我特别喜欢它,因为它是一个没有冒号等的整洁的小单行字,读起来几乎就像一个正常的英语句子。 :)

          在你的情况下,你可以写

          def myFunc(working_list=None):
              working_list = [] if working_list is None else working_list
              working_list.append("a")
              print working_list
          

          【讨论】:

            【解决方案8】:

            也许最简单的事情就是在脚本中创建列表或元组的副本。这避免了检查的需要。例如,

                def my_funct(params, lst = []):
                    liste = lst.copy()
                     . . 
            

            【讨论】:

              【解决方案9】:

              我参加了UCSC扩展课Python for programmer

              以下情况为真:def Fn(data = []):

              a) 是一个好主意,这样您的数据列表在每次调用时都是空的。

              b) 是一个好主意,这样对函数的所有调用(在调用中不提供任何参数)都将获得空列表作为数据。

              c) 是一个合理的想法,只要您的数据是字符串列表。

              d) 是个坏主意,因为默认的 [] 会累积数据,而默认的 [] 会随着后续调用而改变。

              答案:

              d) 是个坏主意,因为默认的 [] 会累积数据,而默认的 [] 会随着后续调用而改变。

              【讨论】:

                猜你喜欢
                • 2021-12-08
                • 2011-01-19
                • 2019-08-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-11-30
                • 2018-05-30
                相关资源
                最近更新 更多