73

I have a Python function that takes a list as a parameter. If I set the parameter's default value to an empty list like this:

def func(items=[]):
    print items

Pylint would tell me "Dangerous default value [] as argument". So I was wondering what is the best practice here?

Liviu
  • 1,859
  • 2
  • 22
  • 48
Jack Z
  • 2,572
  • 4
  • 20
  • 17
  • 6
    this is something that every python newbie stubs their toe on once or twice, it's pretty cool that pylint has stopped you from writing a horrible bug ! – wim Mar 02 '12 at 00:59
  • Another really simple trick is to use an empty tuple: `def func(items=())`. A tuple is not mutable so pylint shuts up, but the question asks for a list so this won't always be relevant or possible. – Ant Sep 04 '19 at 10:28

4 Answers4

107

Use None as a default value:

def func(items=None):
    if items is None:
        items = []
    print items

The problem with a mutable default argument is that it will be shared between all invocations of the function -- see the "important warning" in the relevant section of the Python tutorial.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 3
    Ah wow, I'm surprised that people know about this. =) I hated this feature of python so much that I wrote my own decorator library to allow for `func(items=new([]))` syntax. – ninjagecko Mar 02 '12 at 00:51
  • 1
    Thanks! It seems like Python thinks None is a completely different type than list, so I was wondering is it generally acceptable in Python to set the default value of a parameter to a different type (such as None) (I know Python is a type-less language, but I come from C++... lol)? – Jack Z Mar 02 '12 at 00:54
  • It is quite different to a strongly typed language like C++. Just think of variables as names which reference objects, and everything is passed by reference. – wim Mar 02 '12 at 00:57
  • 3
    @wim: I think you mean 'statically', not strongly; python is strongly but dynamically typed. – DSM Mar 02 '12 at 00:59
  • Thanks, wim! Then when you use a variable, how can you decide if it's an actual list or still keeping its default value None? Or do you always have to check before using it? – Jack Z Mar 02 '12 at 01:00
  • 1
    You don't have to check it every time, only when you expect that it won't be what you think it should be. It's like checking if a pointer/reference is null in C++ .. you don't check it every time, but in many cases it does make sense to guard against it. – cwa Mar 02 '12 at 02:10
  • 7
    I usually write `items = items or []` if I expect `items` to be an iterable type. – Neil G Mar 04 '12 at 21:41
10

I just encountered this for the first time, and my immediate thought is "well, I don't want to mutate the list anyway, so what I really want is to default to an immutable list so Python will give me an error if I accidentally mutate it." An immutable list is just a tuple. So:

  def func(items=()):
      print items

Sure, if you pass it to something that really does want a list (eg isinstance(items, list)), then this'll get you in trouble. But that's a code smell anyway.

sfink
  • 1,726
  • 1
  • 17
  • 22
  • 3
    And if inside the function you need to make a copy, use `my_copy = list(items)`. You came up with a simple and very clever solution to a common problem. – Mark Ransom Sep 17 '13 at 18:12
3

For mutable object as a default parameter in function- and method-declarations the problem is, that the evaluation and creation takes place at exactly the same moment. The python-parser reads the function-head and evaluates it at the same moment.

Most beginers asume that a new object is created at every call, but that's not correct! ONE object (in your example a list) is created at the moment of DECLARATION and not on demand when you are calling the method.

For imutable objects that's not a problem, because even if all calls share the same object, it's imutable and therefore it's properties remain the same.

As a convention you use the None object for defaults to indicate the use of a default initialization, which now can take place in the function-body, which naturally is evaluated at call-time.

Don Question
  • 11,227
  • 5
  • 36
  • 54
1

In addition and also to better understand what python is, here my little themed snippet:

from functools import wraps
def defaultFactories(func):
    'wraps function to use factories instead of values for defaults in call'
    defaults = func.func_defaults
    @wraps(func)
    def wrapped(*args,**kwargs):
        func.func_defaults = tuple(default() for default in defaults)
        return func(*args,**kwargs)
    return wrapped

def f1(n,b = []):
    b.append(n)
    if n == 1: return b
    else: return f1(n-1) + b

@defaultFactories
def f2(n,b = list):
    b.append(n)
    if n == 1: return b
    else: return f2(n-1) + b

>>> f1(6)
[6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1]
>>> f2(6)
[1, 2, 3, 4, 5, 6]
Odomontois
  • 15,918
  • 2
  • 36
  • 71