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


117

有时,使用默认参数(一个空列表)似乎很自然。但是Python在这些情况下会产生意外的行为

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

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

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

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


17
如果有人对此有兴趣,请查看effbot.org/zone/default-values.htm
Ryan Haining

对于集合,也会发生相同的行为,尽管您需要一个稍微复杂一些的示例才能将其显示为错误。
abeboparebop

随着链接的消失,让我明确指出这是期望的行为。默认变量在函数定义时(在第一次调用时发生)进行求值,而不是在每次调用函数时进行求值。因此,如果您更改了可变的默认参数,则任何后续函数调用都只能使用更改后的对象。
莫里茨

Answers:


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

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

文档说您应该将其None用作默认值,并在函数主体中对其进行显式测试


1
最好说:if working_list == None:或者if working_list:??
John Mulder

2
这是在python中执行此操作的首选方法,即使我不喜欢它也是如此,因为它很丑陋。我说最好的做法是“如果working_list为None”。
e-satis

21
此示例中的首选方式是说:如果working_list为None。调用者可能已使用带有自定义附加的空列表状对象。
tzot

5
Mohit Ranka:请注意,如果长度为0,则不是working_list为True。这将导致不一致的行为:如果函数接收到其中包含某些元素的列表,则其调用方将对其列表进行更新,如果列表为空,则该调用者将对其列表进行更新。不会被感动。
森特

1
@PatrickT正确的工具取决于大小写-varargs函数与采用(可选)列表参数的函数有很大不同。您不得不在它们之间进行选择的情况比您想象的要少出现。当arg计数更改时,varargs很好,但是当代码为WRITTEN时,它是固定的。像你的例子。如果运行时变量可变,或者您想调用f()列表,则必须调用f(*l)总值。更糟糕的是,实现mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])将带有varargs的SUCK。如果好得多def mate(birds=[], bees=[]):
FeRD

26

现有答案已经按要求提供了直接解决方案。但是,由于这对于新Python程序员来说是一个非常常见的陷阱,因此有必要添加解释python为何采用这种方式的解释,在《Python的Hitchhikers指南》中很好地总结为“ Mutable Default Arguments ”: http:// docs .python-guide.org / en / latest / writing / gotchas /

Quote:“ Python的默认参数在定义函数时进行一次评估,而不是在每次调用函数时都进行评估(例如Ruby)。这意味着,如果您使用可变的默认参数并对它进行突变,您将拥有将该对象也更改为将来对函数的所有调用

实现它的示例代码:

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

13

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

if working_list is None: working_list = []

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

working_list = working_list or []

尽管如果调用者给您一个空列表(该列表为false)作为working_list并期望您的函数修改他提供的列表,这将出乎意料。


10

如果该函数的目的是修改作为参数传递的参数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())在调用时使用。这样,您的调用约定仍然非常干净。

经常更改可选arg的一种模式递归函数中的隐藏“ memo” 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)

3

我可能没什么意思,但是请记住,如果您只想传递可变数量的参数,则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}

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

-3

我参加了UCSC扩展课程 Python for programmer

def Fn(data = []):

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

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

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

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

回答:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.