Python Essentials for AI Engineer

Course Content

Python Essentials for AI Engineer

6 sections · 48 lessons

What is the difference between a mutable default argument and a safe default argument?


Why two users end up sharing one chat historydef runs once:history = [] is builtThat list isstored onthe functionUser A's callappends to itUser B'scall getsthe same listFix: default to None and build a fresh list inside the call.
The default belongs to the function object, which lives as long as the server process does.

What you need to know

The bug

Python
def add_item(item, bucket=[]):         # unsafe: one list for ALL calls    bucket.append(item)    return bucketprint(add_item(1))                     # [1]print(add_item(2))                     # [1, 2]  <- leaked from the first callprint(add_item.__defaults__)           # ([1, 2],)

__defaults__ shows where the list lives: on the function object itself, created once when def ran. Every call that does not pass bucket gets that same list.

The fix

Python
def add_item(item, bucket=None):    if bucket is None:                 # a new list on every call        bucket = []    bucket.append(item)    return bucketprint(add_item(1), add_item(2))        # [1] [2]

Use if bucket is None, not bucket = bucket or []. The or version also replaces an empty list the caller passed in, so the caller's list never receives the item — a quieter bug.

Why Python works this way

Defaults are part of the function definition, just as the function body is. Evaluating them once is simple and fast, and it is sometimes used on purpose as a cache. The language will not change it, so linters (Ruff rule B006, Pylint W0102) flag mutable defaults instead.

Dataclasses protect you

Python
from dataclasses import dataclass, field@dataclassclass Conversation:    user: str    history: list[str] = field(default_factory=list)   # new list per instancea, b = Conversation("asha"), Conversation("ravi")a.history.append("hi")print(b.history)                       # []

Writing history: list[str] = [] in a dataclass raises a ValueError at class definition time, exactly to stop this bug.

A real-life example

A chatbot service keeps conversation history like this:

Python
def chat(user_msg, history=[]):    history.append({"role": "user", "content": user_msg})    return len(history)print(chat("Hi, I'm Asha. My order 1142 is late."))   # 1print(chat("Hi, I need a refund."))                  # 2  <- a different user!

In a web server the function object lives for the life of the process, so every user who does not pass history shares one list. The second user's prompt now contains the first user's name and order number. The model may repeat that data back — a privacy incident — and the prompt keeps growing until it exceeds the context window. The fix is history=None with a fresh list inside, or better, loading each user's history explicitly from storage.

Follow-up questions to expect

  • "Why does Python evaluate defaults only once?" — Defaults are stored on the function object when def runs; re-evaluating them per call would be a different language design.
  • "Is a tuple default safe?" — Yes, if it only holds immutable items, because nobody can modify it in place.
  • "Is this ever used on purpose?" — Occasionally as a quick cache (def f(x, _cache={})), but functools.lru_cache is clearer.