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

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

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

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

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

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

Sometimes it seems natural to have a default parameter which is an empty list. Yet Python gives unexpected behavior in these situations.

If for example, I have a function:

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

The first time it is called the default will work, but calls after that will update the existing list (with one “a” each call) and print the updated version.

So, what is the pythonic way to get the behavior I desire (a fresh list on each call)?


回答 0

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

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

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

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

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

The docs say you should use None as the default and explicitly test for it in the body of the function.


回答 1

现有答案已经按要求提供了直接解决方案。但是,由于这对于新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

Other answers have already already provided the direct solutions as asked for, however, since this is a very common pitfall for new Python programmers, it’s worth adding the explanation of why Python behaves this way, which is nicely summarized in The Hitchhikers Guide to Python under Mutable Default Arguments:

Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.


回答 2

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

if working_list is None: working_list = []

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

working_list = working_list or []

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

Not that it matters in this case, but you can use object identity to test for None:

if working_list is None: working_list = []

You could also take advantage of how the boolean operator or is defined in python:

working_list = working_list or []

Though this will behave unexpectedly if the caller gives you an empty list (which counts as false) as working_list and expects your function to modify the list he gave it.


回答 3

如果该函数的目的是修改作为参数传递的参数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)

If the intent of the function is to modify the parameter passed as working_list, see HenryR’s answer (=None, check for None inside).

But if you didn’t intend to mutate the argument, just use it as starting point for a list, you can simply copy it:

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

(or in this simple case just print starting_list + ["a"] but I guess that was just a toy example)

In general, mutating your arguments is bad style in Python. The only functions that are fully expected to mutate an object are methods of the object. It’s even rarer to mutate an optional argument — is a side effect that happens only in some calls really the best interface?

  • If you do it from the C habit of “output arguments”, that’s completely unnecessary – you can always return multiple values as a tuple.

  • If you do this to efficiently build a long list of results without building intermediate lists, consider writing it as a generator and using result_list.extend(myFunc()) when you are calling it. This way your calling conventions remains very clean.

One pattern where mutating an optional arg is frequently done is a hidden “memo” arg in recursive functions:

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

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

I might be off-topic, but remember that if you just want to pass a variable number of arguments, the pythonic way is to pass a tuple *args or a dictionary **kargs. These are optional and are better than the syntax myFunc([1, 2, 3]).

If you want to pass a tuple:

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]

If you want to pass a dictionary:

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

回答 5

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

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

There have already been good and correct answers provided. I just wanted to give another syntax to write what you want to do which I find more beautiful when you for instance want to create a class with default empty lists:

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

This snippet makes use of the if else operator syntax. I like it especially because it’s a neat little one-liner without colons, etc. involved and it nearly reads like a normal English sentence. :)

In your case you could write

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

回答 6

我参加了UCSC扩展类 Python for programmer

def Fn(data = []):

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

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

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

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

回答:

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

I took the UCSC extension class Python for programmer

Which is true of: def Fn(data = []):

a) is a good idea so that your data lists start empty with every call.

b) is a good idea so that all calls to the function that do not provide any arguments on the call will get the empty list as data.

c) is a reasonable idea as long as your data is a list of strings.

d) is a bad idea because the default [] will accumulate data and the default [] will change with subsequent calls.

Answer:

d) is a bad idea because the default [] will accumulate data and the default [] will change with subsequent calls.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。