A default value is created once, when Python reads the def line, not each time the function is called. Make it a list or a dict and every call that relies on the default shares the same object.
The surprise
def add_tag(tag, tags=[]): tags.append(tag) return tags print(add_tag("python")) print(add_tag("tips")) # a fresh call... with yesterday's list
['python'] ['python', 'tips']
The fix
Default to None, and build the list inside the function, where it runs on every call.
def add_tag(tag, tags=None): if tags is None: tags = [] tags.append(tag) return tags print(add_tag("python")) print(add_tag("tips"))
['python'] ['tips']
Why it works
The default lives on the function object itself. You can watch it change:
def add_tag(tag, tags=[]): tags.append(tag) return tags add_tag("a") add_tag("b") print(add_tag.__defaults__)
(['a', 'b'],)
None can't be changed in place, so there is nothing to share between calls. The if tags is None check runs every time and hands each call its own list.
When not to worry
Defaults that can't change in place (numbers, strings, tuples, None) are always safe. And if you want a function to remember results between calls, don't lean on this quirk: say so with functools.cache.