← Back to Functions · ← Previous: Type Hints
Earlier chapters showed how functions receive arguments, return values, resolve names, and describe expected types. This chapter adds one more interface decision:
Which inputs must every caller provide, and which ones can have a sensible fallback?
required input
+
defaulted input
↓
caller supplies only what needs to differ
Estimated study time: 75–100 minutes.
Python version: The examples target Python 3.10 or newer, matching the Type Hints chapter.
By the end of this chapter, you should be able to:
- define a default with
name=value; - combine type hints and defaults with
name: type = value; - distinguish required parameters from defaulted parameters;
- override defaults with positional or keyword arguments;
- explain the ordering rule for ordinary required and defaulted parameters;
- explain when default expressions are evaluated;
- recognize the mutable-default trap;
- use
Nonebefore creating a fresh mutable object; - choose defaults that clarify rather than hide required input.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}"
print(greet("Avery"))
print(greet("Avery", "Welcome"))Output:
Hello, Avery
Welcome, Avery
name has no default, so the caller must provide it.
greeting has the default "Hello", so its argument may be omitted.
greet("Avery")
↓
name = "Avery"
greeting = "Hello" ← default fills the missing slot
The basic definition form is:
def function_name(required, optional=default_value):
...
Example:
def build_label(topic, prefix="Topic"):
return f"{prefix}: {topic}"
print(build_label("Functions"))
print(build_label("Functions", prefix="Chapter"))Output:
Topic: Functions
Chapter: Functions
Keep the two uses of = separate:
definition → prefix="Topic" establishes a default
call → prefix="Chapter" supplies a keyword argument
def create_message(name, language="English"):
return f"{name}: {language}"name is required because the function should not invent it.
language is defaulted because "English" is a deliberate fallback.
Ask:
If the caller says nothing about this option, what behavior is reasonable and unsurprising?
Do not add defaults merely to make every argument optional.
def format_score(score, suffix=" points"):
return f"{score}{suffix}"
print(format_score(80))
print(format_score(80, " pts"))Output:
80 points
80 pts
Python uses the default only when the corresponding parameter remains unfilled.
Supplying another value for one call does not change the stored default.
def create_badge(name, color="blue", size="medium"):
return f"{name}: {color}, {size}"
print(create_badge("Python"))
print(create_badge("Python", size="large"))
print(create_badge("Python", color="green"))Output:
Python: blue, medium
Python: blue, large
Python: green, medium
Keyword arguments let a caller change one option without repeating the others.
This is valid:
def register(name, active=True):
return f"{name}: {active}"This is not:
# SyntaxError: non-default argument follows default argument
def register(active=True, name):
return f"{name}: {active}"For ordinary parameters, use this beginner rule:
required parameters first
defaulted parameters after them
Special parameter categories refine the rule later.
def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}"
print(greet("Avery"))Read the signature as:
name: str
├── expected type: str
└── no default → argument required
greeting: str = "Hello"
├── expected type: str
└── default: "Hello" → argument may be omitted
-> str
└── expected return type
Type hints describe expected types. Defaults describe omitted-argument behavior.
Neither concept replaces runtime validation.
default → fallback when an argument is omitted
type hint → expected type information
validation → checks actual values or rules
conversion → explicitly transforms compatible data
For example:
def repeat_text(text: str, times: int = 2) -> str:
return text * timestimes=2 is a fallback. It does not validate every later value.
def create_heading(title: str, level: int = 2) -> str:
return f"h{level}: {title}"The interface communicates:
If the caller does not choose a level, use 2.
Changing the default later changes every call that omits level.
Defaults are small interface decisions, not merely shorter syntax.
This design can hide missing information:
def create_student(name="", course=""):
...If both pieces are necessary, require them:
def create_student(name: str, course: str, active: bool = True):
...Now only active has a deliberate fallback.
A shorter call is not automatically a clearer interface.
level = "beginner"
def describe(topic, course_level=level):
return f"{topic}: {course_level}"
level = "advanced"
print(describe("Functions"))
print(describe("Functions", level))Output:
Functions: beginner
Functions: advanced
When the def statement ran, level was "beginner".
That value became the stored default for course_level.
Changing the external variable later does not recalculate the default.
Use this mental model:
execute def statement
↓
evaluate default expressions
↓
store their resulting values
↓
future calls reuse stored defaults when needed
This matters most when the stored object can change.
Strings, numbers, booleans, and None are common defaults:
def describe_course(
name: str,
level: str = "beginner",
lessons: int = 10,
published: bool = False,
) -> str:
return f"{name} | {level} | {lessons} | {published}"These values are immutable, so they do not create the shared-mutation problem shown next.
You should still ask whether each fallback is sensible.
def add_topic(topic, topics=[]):
topics.append(topic)
return topics
print(add_topic("functions"))
print(add_topic("defaults"))Output:
['functions']
['functions', 'defaults']
The same list is reused because it was created when the function definition executed.
This is the mutable default argument trap.
Lists are fine inside function bodies:
def create_topics():
topics = []
topics.append("functions")
return topicsA new list is created each time the body runs.
The risky form is specifically:
def add_topic(topic, topics=[]):
...because that list belongs to the stored defaults and can survive across calls.
def add_topic(topic: str, topics: list[str] | None = None) -> list[str]:
if topics is None:
topics = []
topics.append(topic)
return topics
print(add_topic("functions"))
print(add_topic("defaults"))Output:
['functions']
['defaults']
Each omitted topics argument first produces None, then the body creates a fresh list.
Here, None means:
No list was supplied, so create one now.
topics supplied?
├── yes → use that object
└── no → default gives None
↓
create a fresh list
This works when None is not itself meaningful application data for that parameter.
Custom sentinels are an advanced interface topic and are outside this chapter.
def add_topic(topic: str, topics: list[str] | None = None) -> list[str]:
if topics is None:
topics = []
topics.append(topic)
return topics
planned = ["scope"]
result = add_topic("defaults", planned)
print(planned)
print(result)Output:
['scope', 'defaults']
['scope', 'defaults']
The safe default pattern does not copy an object explicitly supplied by the caller.
Shared default state and deliberate mutation of caller-owned data are separate questions.
def power(base, exponent=2):
return base ** exponent
print(power(5))
print(power(5, 3))
print(power(5, exponent=3))Output:
25
125
125
For optional settings, a keyword often makes the caller's intention clearer.
def export_summary(name, format="text", include_title=True):
return f"{name}: {format}, title={include_title}"
print(export_summary("study", include_title=False))Output:
study: text, title=False
There is no blank positional placeholder for “keep this default but change the next one”.
Keyword arguments provide selective overrides.
A default can be any suitable value:
def format_name(name, separator=", "):
...Use None when it accurately represents the omitted-argument case you need, especially when creating a fresh mutable object.
Do not replace every default with None mechanically.
Avoid:
def collect_item(item, items=[]):
items.append(item)
return itemsPrefer:
def collect_item(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return itemsAvoid:
# SyntaxError
def connect(timeout=30, host):
return host, timeoutPrefer:
def connect(host, timeout=30):
return host, timeoutIf topic is truly required, do not hide that decision:
def study(topic):
return topicdef create_title(topic: str, prefix: str = "Chapter", number: int = 1) -> str:
return f"{prefix} {number}: {topic}"
title = create_title("Defaults", number=6)
print(title)Trace:
1. call create_title("Defaults", number=6)
2. topic = "Defaults"
3. number = 6
4. prefix is unfilled
5. prefix receives stored default "Chapter"
6. body returns "Chapter 6: Defaults"
7. title receives that returned string
Every parameter has a value before the body runs, either from a supplied argument or a default.
def greet(name: str, greeting: str = "Hello", punctuation: str = "!") -> str:
return f"{greeting}, {name}{punctuation}"
print(greet("Avery"))
print(greet("Avery", greeting="Welcome"))
print(greet("Avery", punctuation="."))Output:
Hello, Avery!
Welcome, Avery!
Hello, Avery.
def calculate_shipping(weight: float, rate: float = 2.5, handling: float = 3.0) -> float:
return weight * rate + handling
print(calculate_shipping(4.0))
print(calculate_shipping(4.0, rate=3.0))
print(calculate_shipping(4.0, handling=0.0))Output:
13.0
15.0
10.0
def add_task(task: str, tasks: list[str] | None = None) -> list[str]:
if tasks is None:
tasks = []
tasks.append(task)
return tasks
print(add_task("study"))
print(add_task("practice"))
print(add_task("review", ["plan"]))Output:
['study']
['practice']
['plan', 'review']
The first two calls create independent lists. The third deliberately modifies the supplied list.
definition and call
↓
parameters and arguments
↓
return values
↓
scope
↓
type hints
↓
default values
↓
required vs optional caller input
Defaults do not replace arguments. They define how a parameter receives a value when its argument is omitted.
Before adding a default, ask:
- Is this input truly optional?
- Is the fallback unsurprising?
- Would changing it later alter important behavior?
- Is the default mutable?
- If it is mutable, should
Nonetrigger a fresh object? - Is
Noneitself meaningful data here? - Would a keyword override make the call clearer?
- Does the type hint include
NonewhenNoneis supported?
This chapter focuses on ordinary defaults for regular function parameters.
It does not require:
- positional-only parameters with
/; - keyword-only design with
*; *argsand**kwargs;- custom sentinel objects;
- decorators;
- advanced typing constructs;
- dataclasses or class constructors.
The next chapter introduces *args and **kwargs.
Create build_reminder.
Requirements:
taskis required;prioritydefaults to"normal";donedefaults toFalse;- use type hints;
- return one formatted string;
- call it once using both defaults;
- call it again overriding only
priorityby keyword.
print(build_reminder("Study Python"))
print(build_reminder("Review functions", priority="high"))Create another function with an optional list:
- do not use
[]directly as the default; - use
None; - create a fresh list inside the body;
- demonstrate that two calls without a list do not share state.
- What does
language="English"mean in a definition? - When does Python use a default?
- What happens when the caller supplies that argument?
- Why do ordinary required parameters normally appear first?
- When are default expressions evaluated?
- Why can
items=[]share state across calls? - How does the
Nonepattern avoid that problem? - Does a default validate an argument?
- How are type hints and defaults different?
- Why should a default represent genuinely optional behavior?
def greet(name, greeting="Hello"):
return f"{greeting}, {name}"def greet(name: str, greeting: str = "Hello") -> str:
return f"{greeting}, {name}"greet("Avery", greeting="Welcome")def add_item(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return itemsdefault
→ used when the corresponding argument is omitted
default expression
→ evaluated when the function definition executes
mutable default object
→ can be shared between calls
None sentinel pattern
→ create a fresh mutable object inside the body
python functions/06-default-values/examples/greet_with_style.py
python functions/06-default-values/examples/shipping_quote.py
python functions/06-default-values/examples/safe_list_default.py- Python 3.13 Tutorial — Default Argument Values
- Python 3.13 Language Reference — Function definitions
- Python 3.13 Language Reference — Calls
Next: 07. *args and **kwargs.