Default Arguments in Python

Default Arguments in Python

Tags
Python
Published
July 2, 2022
Author
yanbc

Something strange

Recently, I encountered a rather absurd behavior with python functions. The following code will give you the gist of it:
def func(L=[]): L.append(1) return L print(func()) print(func()) print(func())
Before you continue, I want you to run it in your head. What do you think the script will print? Here is what I thought:
[1] [1] [1]
However, the actual output in my run:
[1] [1, 1] [1, 1, 1]
Weird right? As a programmer, I would expect the function default argument to be the same every time it runs. It almost seems like that the function default argument (the empty list in our case) got initialized only once and is reused in later calls … Wait, we can check that!
def func(L=[]): print(hex(id(L))) L.append(1) return L func() func() func()
The above code outputs (Note that your run might print out different values depending on a hell lot of things):
0x7faeb408df88 0x7faeb408df88 0x7faeb408df88
We can see that the memory address of the default argument never changes. So python does use the same default value over time.

What to make of it?

What if I were to tell you that this is completely normal and fits the pythonic idiom perfectly? Hear me out.
Remember, functions are first-class objects in Python. And the def statement is an executable command where functions got created. Consider the following code:
def make_empty_list(): print("making empty list") new_empty = [] return new_empty def func(L=make_empty_list()): L.append(1) return L print(func()) print(func()) print(func()) ## output: # making empty list # [1] # [1, 1] # [1, 1, 1]
Instead of passing an empty list directly, we ask the interpreter to evaluate make_empty_list and pass the result as the default argument of L. Note that the string making empty list is printed when def func(L=make_empty_list()) is executed, and not when func() is called.
As a matter of fact, there is a function attribute called __defaults__ in all function objects where default arguments are stored (Since Python 3.0, I think. In Python 2, it’s func_defaults).
def make_empty_list(): print("making emtpy list") new_empty = [] return new_empty def func(L=make_empty_list()): L.append(1) return L print(func.__defaults__) func() print(func.__defaults__) ## output: # making emtpy list # ([],) # ([1],)

What to do about it?

I will admit, that using mutable variables as default arguments can be confusing at times. So to avoid confusion and bugs, a common practice is to use a dummy/placeholder and initialize the default arguments inside function execution.
def func(L:list=None): if L is None: L = [] # do your processing here return L
And in cases where None is also an acceptable input:
import enum class __Dummy(enum.Enum): PlaceHolder = 1 def func(L:list=__Dummy.PlaceHolder): if L == __Dummy.PlaceHolder: L = [] # do your processing here return L

References