Mutable types as default params in Python

Posted on Thu, 09 Jun 2016 in Python

Do you ask yourself why you souldn't use mutable types as default value for parameters in Python? I'm sure you ask at least if you are not a junior. Probably, your answer is "It causes to strange side effects". However, only a tiny part of python developers makes step throw and figure out how it works under the hood.

At the beginning, we should find out what kind of side effects we deal with. Look at this example:

def foo(param=[]):
    print(param)

>>> foo()
[]

There are no side effects in this function. It's good. That's what we expect. But if we modify it this way:

def foo(param=[]):
    param.append(1)
    print(param)

We will find that function prints different values depends on how many times we use it.

>>> foo()
[1]
>>> foo()
[1,1]
>>> foo()
[1,1,1]

We don't want this. Such function behaviour in Python is the leader of "Top strange things of Python" lists. Although if you make some efforts to figure out how Python acts under the hood, this "strange" behaviour will become consistent and logical.

In Python function is object like all other things in that language. Then it creates function object, it packs default param values into foo.func_defaults tuple.

List in Python is mutable. Python makes function object ones. This results that object foo.func_defaults[0] and param inside function point to the same object in memory.

def foo(param=[]):
    print(id(param))

>>> foo()
4494207600
>>> id(foo.func_defaults[0])
4494207600

You should use None as default value instead list, dict or any other mutable types and initialize mutable object inside function body. To hint type use docstring or PEP 0484 if you use Python 3.5+.

def foo(param=None):
    """
    :type param: list
    """
    param = [] if param is None else param
    param.append(1)
    print(param)

>>> foo()
[1]
>>> foo()
[1]

For short functions adding this kind of checks makes them less readable. In such cases you should decide to leave this code and make a function safer or remove it. I choose safety. You can miss changes that add side effects doing refactoring later.