什么是避免作为空列表的默认参数的pythonic方法?

时间:2008-12-14 11:23:55

标签: python python-3.x

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

例如,如果我有一个功能:

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

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

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

9 个答案:

答案 0 :(得分:107)

def my_func(working_list=None):
    if working_list is None: 
        working_list = []

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

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

答案 1 :(得分:14)

现有答案已经提供了所要求的直接解决方案。但是,由于这对于新的Python程序员来说是一个非常常见的陷阱,因此值得添加解释为什么python以这种方式运行,这很好地总结在" Hitchhikers Python指南&#34 ; as" 可变默认参数": http://docs.python-guide.org/en/latest/writing/gotchas/

引用:" Python的默认参数在定义函数时被评估一次,而不是每次调用函数时(比如Ruby)。这意味着如果你使用一个可变的默认参数并对其进行变异,你将会为该函数的所有未来调用而改变该对象"

实施它的示例代码:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

答案 2 :(得分:11)

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

if working_list is None: working_list = []

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

working_list = working_list or []

如果调用者给你一个空列表(计为false)并且你的函数修改了他给出的列表,那么这会出现意外行为。

答案 3 :(得分:10)

如果函数的意图是修改作为working_list传递的参数,请参阅HenryR的答案(=无,检查里面的无)。

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

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

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

一般来说,改变你的参数是Python中糟糕的风格。完全期望改变对象的唯一函数是对象的方法。改变一个可选参数甚至更为罕见 - 这种副作用只发生在某些调用真的是最好的接口吗?

  • 如果你从“输出参数”的习惯中做到这一点,那是完全没必要的 - 你总是可以将多个值作为元组返回。

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

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

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)

答案 4 :(得分:1)

我可能偏离主题,但请记住,如果您只想传递可变数量的参数,则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}

答案 5 :(得分:1)

引用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

答案 6 :(得分:0)

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

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 []

此代码段使用if else运算符语法。我喜欢它特别是因为它是一个整洁的小单行,没有冒号等涉及它几乎读起来像一个普通的英语句子。 :)

在你的情况下你可以写

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:-1)

我参加了UCSC扩展类Python for programmer

以下哪一项是正确的:def Fn(data = []):

  

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

     

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

     

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

     

d)是个坏主意,因为默认值[]将累积数据,默认值[]将随后续调用而更改。

答案:

  

d)是个坏主意,因为默认值[]将累积数据,默认值[]将随后续调用而更改。