make_report_line yesterday took a rank argument — if you called it without one, Python threw an error. What would it look like if rank were optional and defaulted to something sensible?
I'd want the caller to be able to skip it. Like an Excel function where some arguments are in brackets in the help text — you can leave them out and the function fills in a default.
Exactly that pattern. In Python you write def make_report_line(name, spend, rank=1): and the =1 is the default. A caller who passes a rank gets theirs; a caller who skips it gets 1. No overloading, no if-blocks — one signature handles both:
def label_campaign(name: str, spend: float, channel: str = "all") -> str:
return f"[{channel}] {name}: ${spend:,.2f}"
print(label_campaign("Email Q2", 1250.0)) # [all] Email Q2: $1,250.00
print(label_campaign("Email Q2", 1250.0, "email")) # [email] Email Q2: $1,250.00I get that the default fills in — but if channel_summary needs to return spend, leads, and CPL, that's three values. Does Python return a list? Or do I build a dict like Day 14?
Neither. Python lets you return multiple values as a tuple — a fixed, ordered group. Write return total_spend, total_leads, avg_cpl and Python packages the three into one tuple automatically. On the receiving end, the caller unpacks with spend, leads, cpl = summarize_channel(campaigns) — three variables filled in one line. It reads exactly like a named range formula that outputs a row of three cells.
So the function is one line out, the caller is one line in. That's cleaner than a dict for a fixed set of three numbers.
It is — until you need a fourth value six months from now and every caller breaks. Fixed output, use a tuple. Variable shape, use a dict. For a tight three-metric summary that won't grow, a tuple is exactly right.
And combining both ideas — the default arg filters the campaigns before the aggregation, so summarize_channel(campaigns) is the whole portfolio and summarize_channel(campaigns, "email") is just one channel. One function, two modes.
That's the design. The default is a contract: omit the arg and get the broadest, most useful result. Require it and you narrow to exactly what you need. Put it together:
def summarize_channel(campaigns: list, channel: str = "all") -> tuple:
filtered = campaigns if channel == "all" else [c for c in campaigns if c["channel"] == channel]
total_spend = sum(c["spend"] for c in filtered)
total_leads = sum(c["leads"] for c in filtered)
avg_cpl = total_spend / total_leads if total_leads > 0 else 0.0
print(f"Summarised: {channel} — {len(filtered)} campaigns")
return total_spend, total_leads, avg_cpl
spend, leads, cpl = summarize_channel(campaigns)A default argument (channel: str = "all") makes a parameter optional. Callers who pass a value get theirs; callers who skip it get the default. Defaults must come after required params.
return a, b, c packs three values into a single tuple. The caller unpacks with:
spend, leads, cpl = summarize_channel(campaigns)| Pattern | When to use |
|---|---|
| Tuple return | Fixed number of related values |
| Dict return | Variable shape or named fields needed later |
def f(items=[]): — the list is shared across calls. Use None and assign inside.summarize_channel(campaigns, "email") and summarize_channel(campaigns, channel="email") are identical; keyword form is self-documenting.make_report_line yesterday took a rank argument — if you called it without one, Python threw an error. What would it look like if rank were optional and defaulted to something sensible?
I'd want the caller to be able to skip it. Like an Excel function where some arguments are in brackets in the help text — you can leave them out and the function fills in a default.
Exactly that pattern. In Python you write def make_report_line(name, spend, rank=1): and the =1 is the default. A caller who passes a rank gets theirs; a caller who skips it gets 1. No overloading, no if-blocks — one signature handles both:
def label_campaign(name: str, spend: float, channel: str = "all") -> str:
return f"[{channel}] {name}: ${spend:,.2f}"
print(label_campaign("Email Q2", 1250.0)) # [all] Email Q2: $1,250.00
print(label_campaign("Email Q2", 1250.0, "email")) # [email] Email Q2: $1,250.00I get that the default fills in — but if channel_summary needs to return spend, leads, and CPL, that's three values. Does Python return a list? Or do I build a dict like Day 14?
Neither. Python lets you return multiple values as a tuple — a fixed, ordered group. Write return total_spend, total_leads, avg_cpl and Python packages the three into one tuple automatically. On the receiving end, the caller unpacks with spend, leads, cpl = summarize_channel(campaigns) — three variables filled in one line. It reads exactly like a named range formula that outputs a row of three cells.
So the function is one line out, the caller is one line in. That's cleaner than a dict for a fixed set of three numbers.
It is — until you need a fourth value six months from now and every caller breaks. Fixed output, use a tuple. Variable shape, use a dict. For a tight three-metric summary that won't grow, a tuple is exactly right.
And combining both ideas — the default arg filters the campaigns before the aggregation, so summarize_channel(campaigns) is the whole portfolio and summarize_channel(campaigns, "email") is just one channel. One function, two modes.
That's the design. The default is a contract: omit the arg and get the broadest, most useful result. Require it and you narrow to exactly what you need. Put it together:
def summarize_channel(campaigns: list, channel: str = "all") -> tuple:
filtered = campaigns if channel == "all" else [c for c in campaigns if c["channel"] == channel]
total_spend = sum(c["spend"] for c in filtered)
total_leads = sum(c["leads"] for c in filtered)
avg_cpl = total_spend / total_leads if total_leads > 0 else 0.0
print(f"Summarised: {channel} — {len(filtered)} campaigns")
return total_spend, total_leads, avg_cpl
spend, leads, cpl = summarize_channel(campaigns)A default argument (channel: str = "all") makes a parameter optional. Callers who pass a value get theirs; callers who skip it get the default. Defaults must come after required params.
return a, b, c packs three values into a single tuple. The caller unpacks with:
spend, leads, cpl = summarize_channel(campaigns)| Pattern | When to use |
|---|---|
| Tuple return | Fixed number of related values |
| Dict return | Variable shape or named fields needed later |
def f(items=[]): — the list is shared across calls. Use None and assign inside.summarize_channel(campaigns, "email") and summarize_channel(campaigns, channel="email") are identical; keyword form is self-documenting.Dev's VP of Marketing wants a fast summary of campaign performance — either across the whole portfolio or for one named channel. Write `summarize_channel(campaigns, channel="all")` that takes a list of campaign dicts (each with `"channel"`, `"spend"`, and `"leads"` keys) and an optional channel filter (default `"all"`). When `channel == "all"` use every campaign; otherwise filter to the named channel only. Return a tuple `(total_spend, total_leads, avg_cpl)` where `avg_cpl` is `total_spend / total_leads` guarded by `0.0` when leads is zero.
Tap each step for scaffolded hints.
No blank-editor panic.