What is pythonic to avoid default options that are empty lists?

Sometimes it seems natural to have a default parameter, which is an empty list. However, 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) 

At the first call, it will work by default, but after that, the calls will update the existing list (one call to "a") and print the updated version.

So, what is the python way to get the desired behavior (a new list on every call)?

+79
python
Dec 14 '08 at 11:23
source share
7 answers
 def my_func(working_list=None): if working_list is None: working_list = [] working_list.append("a") print(working_list) 

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

+108
Dec 14 '08 at 11:27
source share

Existing answers have already provided direct solutions as requested. However, since this is a very common mistake for new Python programmers, it’s worth adding an explanation of why python behaves in a way that is well presented in the Python Auto-Stop Guide as β€œ Variable Default Arguments ”: http: // docs. python-guide.org/o/last/record/dumps /

Quote: β€œPythons arguments by default are evaluated once when a function is defined, and not every time a function is called (as in Ruby, for example). This means that if you use a mutable default argument and change it, you will have mutated this object for all future function calls

Sample code for its implementation:

 def foo(element, to=None): if to is None: to = [] to.append(element) return to 
+17
May 30, '17 at 21:15
source share

It doesn’t matter in this case, but you can use the object identifier to test None:

 if working_list is None: working_list = [] 

You can also take advantage of how a boolean operator is or is defined in python:

 working_list = working_list or [] 

Although this will behave unexpectedly if the caller provides you with an empty list (which is considered false) as the working directory and expects your function to change the list that it gave it.

+11
Dec 14 '08 at 11:43
source share

If the purpose of the function is to change the parameter passed as working_list , see HenryR's answer (= No, check for No inside).

But if you did not intend to change the argument, just use it as a starting point for the 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 think it was just a toy example)

In general, changing your arguments is a bad style in Python. The only functions that are expected to mutate an object are object methods. It is even less common to mutate an optional argument - is the side effect that occurs only on certain calls really a better interface?

  • If you do this out of C's habit of "output arguments", this is completely unnecessary - you can always return multiple values ​​as a tuple.

  • If you do this to effectively build a long list of results without creating intermediate lists, result_list.extend(myFunc()) write it as a generator and use result_list.extend(myFunc()) when it is called. This way, your calling conventions remain very clean.

One example where mutation of an optional argument is often done is the hidden memo argument 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) 
+10
Jan 07 '10 at 16:09
source share

I could be off topic, but remember that if you just want to pass a variable number of arguments, the pythonic way is to pass a *args tuple or a **kargs . They are optional and 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} 
+1
Dec 15 '08 at 8:33
source share

There are already good and correct answers. I just wanted to give another syntax to write what you want to do, which I find more beautiful when, for example, you want to create a class with empty default 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 uses the if else syntax. I especially like it because it is a neat little liner without colons, etc., and it almost reads like a normal English sentence. :)

In your case, you can write

 def myFunc(working_list=None): working_list = [] if working_list is None else working_list working_list.append("a") print working_list 
0
Aug 04 '17 at 9:57 on
source share

I took the UCSC Python for programmer extension class Python for programmer

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

a) It’s a good idea that your data lists start empty with every call.

b) a good idea, so that all function calls that do not provide arguments when called will receive an empty list as data.

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

d) a bad idea, because default [] will accumulate data, and default [] will change on subsequent calls.

Answer:

d) a bad idea, because default [] will accumulate data, and default [] will change on subsequent calls.

0
Jun 30 '19 at 7:01
source share



All Articles