views:

312

answers:

4

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 myFunc(working_list = []):
    working_list.append("a")
    print working_list

The first time it is called with the default will work, but calls after that will use a constantly updating list.

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

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

is how I do it.

HenryR
Is it better to say: if working_list == None:or if working_list:??
John Mulder
if not working_list:
Mohit Ranka
This is the preferred way to do it in python, even if I don't like it since it's ugly. I'd say the best practice would be "if working_list is None".
e-satis
The preferred way in this example is to say: if working_list is None . The caller might have used an empty list-like object with a custom append.
ΤΖΩΤΖΙΟΥ
Modified according to PEP8 http://www.python.org/dev/peps/pep-0008/
J.F. Sebastian
Mohit Ranka: beware that not working_list is True if its length is 0. This leads to a inconsistent behaviour : if the functions receives a list with some element in it, its caller will have his list updated, and if the list is empty, it won't be touched.
vincent
+4  A: 

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.

bendin
A: 

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}
Mapad
+2  A: 

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.

  • 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.

Beni Cherniavsky-Paskin