Sometimes it seems natural to have a default parameter which is an empty list. Yet Python produces 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)?
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 say you should use None
as the default and explicitly test for it in the body of the function.
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.
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.
or
suggestion looks nice, but it behaves suprisingly when supplied with 0
vs. 1
or True
vs. False
.
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)
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}
Quote from https://docs.python.org/3/reference/compound_stmts.html#function-definitions
Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function, e.g.:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
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
Perhaps the simplest thing of all is to just create a copy of the list or tuple within the script. This avoids the need for checking. For example,
def my_funct(params, lst = []):
liste = lst.copy()
. .
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.
Success story sharing
f()
on a list, you'd have to callf(*l)
which is gross. Worse, implementingmate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])
would SUCK w/ varargs. Much better if it'sdef mate(birds=[], bees=[]):
.