## Introduction

This post is an exploration into when a python function gets the default argument from a function signature. Here is the scenario that got me to this point:

from datetime import datetime
from time import sleep

def save_file_w_timestamp(filename=f'{datetime.now().isoformat()}/file.csv'):
print(filename)

save_file_w_timestamp()
sleep(5)
save_file_w_timestamp()

2022-02-02T21:00:58.160733/file.csv
2022-02-02T21:00:58.160733/file.csv


My initial thought was that these two function calls would return the time that the function was called, but it doesn't. That is what we will explore in this blog post.

Let's explore how to make this work as expected.

## If you're in a hurry:

def save_file_w_None(filename=None):
if filename is None: filename=f'{datetime.now().isoformat()}/file.csv'
print(filename)


## Failed Attempt #1: passing through a function

def get_current_datetime():
return datetime.now().isoformat()

def save_file_w_timestamp(filename=f'{get_current_datetime()}/file.csv'):
print(filename)

save_file_w_timestamp()
sleep(5)
save_file_w_timestamp()

2022-02-02T21:01:03.198774/file.csv
2022-02-02T21:01:03.198774/file.csv


## Attempt #2: Brute force

def save_file_w_timestamp(filename=None):
if filename is None: filename=f'{datetime.now().isoformat()}/file.csv'
print(filename)

save_file_w_timestamp()
sleep(5)
save_file_w_timestamp()

2022-02-02T21:01:17.708874/file.csv
2022-02-02T21:01:22.712349/file.csv


## Exciting Attempt #3: Mutable Madness

This third example surprised me a lot (thank you to miwojc on the fastai discord for bringing it to my attention!). If you have an empty list as your default value, it seems innocent enough. Naive Kevin from yesterday would have assumed that this code would create an empty list if x was not passed. Naive Kevin would be sadly mistaken. This is a really good example of what is actually happening above. This creates a variable x that starts as an empty list, but let's see what happens when we call the function.

def mutable_madness(x=[]):
x.append(1)
print(x)

mutable_madness()

[1]


What do you think the value is going to be here?

mutable_madness()


[1, 1]


How about if we pass an empty list in?

mutable_madness([])


[1]


mutable_madness()


[1, 1, 1]


I got all of these wrong when I was initially coding this so if it doesn't seem intuitive to you, just know you aren't alone. This is a fairly common gotcha that can lead to frustrating bugs. Here is another good blog post for further reading: https://docs.python-guide.org/writing/gotchas/.

Just to explore a few more ideas from this concept, I am going to add a few more examples below.

j=1
#global j #This could be added to allow j to be used inside and outside the function.
y+=1
print(y)


My initial thought with this example was that j would keep incrementing because we are setting j inside of our function. This is actually a good lesson about context which I won't get into a ton except to mention that the j in line 1 and line 2 are the same j and the j in line 3 is a different j which is only accessible inside of the function. If we wanted this to behave similarly to the functions above which kept using the default value from above, the global argument would need to be added but this really is using a different concept to keep incrementing the value once we introduce global.

immutable_nonmadness()

2

immutable_nonmadness()


2


In this example, because the value 1 is an immutable object, it doesn't hold onto the previous value but if instead, we had put an empty list in j, it would act the same way as the examples from above. This is because a variable is neither mutable nor immutable. A variable is give its type and therefor its mutability based on the object it is storing.

j=[]
def function_1(y=j):
y+=[1]
print(y)

function_1()

[1]

function_1()

[1, 1]


This behavior will happen with lists, dicts, sets, and most custom classes.

def function_dict(value, x={}):
x[value] = len(x)
print(x)

function_dict('thing 0')

{'thing 0': 0}

function_dict('thing 1')

{'thing 0': 0, 'thing 1': 1}

def function_set(value, x=set()):

function_set('thing 0')

{'thing 0'}

function_set('thing 1')

{'thing 0', 'thing 1'}