dict.setdefault()
dict.get that also writes: missing keys get the default stored, then returned.
Common call
groups.setdefault(key, []).append(item)
Returns
existing value, or the default (now stored)
Replaces
get reads only; setdefault reads AND writes on miss
Watch out
the default is evaluated even when the key exists
dict.setdefault(keykey — The key to look up (and possibly create).type: hashable · required, defaultdefault — Stored under key and returned when the key is absent.type: Any · default: None=None)
→ Any
Demo
Live evaluation
Try:
Inputs
dictdictkey: value pairs
keystrkey to look up
defaultAnyempty = None
Output
{'a': '1', 'b': '2'}.setdefault('a', '0')
'1'
The demo shows the RETURN value. The side effect is the point though: on a miss, real Python also stores the default — after {"a": 1}.setdefault("z", 0) the dict is {"a": 1, "z": 0}.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| key | hashable | yes | The key to look up (and possibly create). |
| default | Any | no (None) | Stored under key and returned when the key is absent. |
Return value
Any — The existing value when the key is present; otherwise the default — which is also STORED under the key.
Common patterns
Grouping into lists
The canonical setdefault idiom — initialize-and-append in one line.
groups = {} for item in items: groups.setdefault(item.kind, []).append(item)
Nested dict creation
Build one level at a time without membership checks.
tree.setdefault(a, {}).setdefault(b, {})[c] = value
First-write-wins cache
Later calls with the same key keep the original value.
canonical = seen.setdefault(name.lower(), name)
Examples
1. Key present — value returned, dict untouched
{"a": 1}.setdefault("a", 0)
Returns
12. Key missing — default stored and returned
{"a": 1}.setdefault("z", 0)
Returns
03. Default defaults to None
{}.setdefault("k")
Returns
NonePitfalls
1. The default is evaluated eagerly
Even when the key exists, the default expression runs — costly defaults hurt.
Wasted work
d.setdefault(k, expensive())
expensive() runs on every call
Fix
if k not in d: d[k] = expensive()
expensive() only on miss
2. Mutable default is shared per call, not per key
Each setdefault call needs a FRESH list/dict literal — do not hoist it.
Shared list
empty = [] d.setdefault("a", empty).append(1) d.setdefault("b", empty).append(2)
d['a'] is d['b'] — both [1, 2]
Fix
d.setdefault("a", []).append(1) d.setdefault("b", []).append(2)
{'a': [1], 'b': [2]}
3. Heavy grouping wants defaultdict
setdefault re-evaluates the default per call; defaultdict builds it only on miss.
Fine but noisy
groups.setdefault(k, []).append(x)
new [] built every call
At scale
from collections import defaultdict groups = defaultdict(list) groups[k].append(x)
[] built only on first miss
When to use
Use it
- Occasional initialize-if-missing on a plain dict
- Grouping / nesting without pre-checks
- First-write-wins registries
Reach for something else
- Read-only default → dict.get
- Every key needs the same factory → collections.defaultdict
- Expensive defaults → explicit "if key not in d" guard
Notes
Complexity
O(1) average — single hash lookup, insert on miss
Return
stored value; dict mutates only on miss
CPython impl
Objects/dictobject.c :: dict_setdefault_impl
Memory
Inserts one entry on miss
Thread-safe
The lookup-insert is atomic in CPython — handy for simple caches
FAQ
When most lookups create the key and the default comes from a factory (list, int, set). setdefault wins for one-off use on dicts you do not control the type of.
History
2.0
Core dict method, unchanged semantics since.