Python Dictionaries — Language Specification 1. Spec Reference Primary source: Python Language Reference, §3.2 — Mapping types https://docs.python.org/3/reference/datamodel.html#mapping-types Built-in dict type: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict Dict display grammar: §6.2.6 https://docs.python.org/3/reference/expressions.html#dictionary-displays collections.OrderedDict, defaultdict, ChainMap, Counter: https://docs.python.org/3/library/collections.html typing.TypedDict: https://docs.python.org/3/library/typing.html#typing.TypedDict Python 3.12 standard: All rules here apply to CPython 3.12 unless otherwise noted. 2.1 Dictionary Display (Literal) dict_display ::= "{" [key_datum_list | dict_comprehension] "}"
key_datum_list ::= key_datum ("," key_datum)* [","]
key_datum ::= expression ":" expression
| "**" or_expr
dict_comprehension::= expression ":" expression comp_for
2.2 Subscription (Key Lookup and Assignment) subscription ::= primary "[" expression_list "]"
# For dict: key lookup → d[key]
# For dict: key assignment → d[key] = value
# For dict: key deletion → del d[key]
2.3 Dict Merge Operators (Python 3.9+, PEP 584) merge_expr ::= dict_expr "|" dict_expr # returns new dict
update_expr ::= dict_expr "|=" dict_expr # in-place update (augmented assignment)
3. Core Rules and Constraints 3.1 Dictionary Characteristics Ordered: Since Python 3.7, dicts preserve insertion order (was implementation detail in 3.6, guaranteed in 3.7). Mutable: key-value pairs can be added, updated, or deleted. Keys must be hashable: keys must define __hash__ and __eq__. Values can be any object: no restriction on value types. Keys are unique: duplicate key assignment overwrites the previous value. No duplicate keys in a literal: {"a": 1, "a": 2} is valid syntax; the second value wins. 3.2 Key Lookup d[key] raises KeyError if key is not present. d.get(key) returns None if not present (no exception). d.get(key, default) returns default if not present. Lookup uses hash(key) then key.__eq__ for collision resolution. 3.3 Insertion Order (Python 3.7+) dict is ordered by insertion order. When iterating with for k in d, d.keys(), d.values(), d.items() — order is insertion order. Updating an existing key does not change its position; deletion and re-insertion moves it to the end. 3.4 Key Equality and Hashability Two keys are considered equal if hash(k1) == hash(k2) and k1 == k2. 1 and True are equal (True == 1, hash(True) == hash(1) == 1); they are the same key. 1.0 and 1 are equal (1.0 == 1, hash(1.0) == hash(1)); same key. list, set, dict cannot be keys (unhashable). tuple and frozenset can be keys (if all elements are hashable). 3.5 Dict Merging (Python 3.9+) d1 | d2 creates a new dict; for duplicate keys, d2 values win. d1 |= d2 updates d1 in-place; for duplicate keys, d2 values win. Before Python 3.9: use {**d1, **d2} or d1.update(d2). 3.6 Iteration During Mutation Adding or removing keys during iteration over a dict raises RuntimeError in CPython 3.3+. Modifying the value of an existing key during iteration is safe. 4. Type Rules (Dunder Methods and Protocols) 4.1 Mapping Protocol object . __getitem__ ( self , key ) -> value
# Called by d[key]. Raises KeyError if key absent.
object . __setitem__ ( self , key , value )
# Called by d[key] = value.
object . __delitem__ ( self , key )
# Called by del d[key]. Raises KeyError if absent.
object . __iter__ ( self ) -> iterator
# Iterates over keys.
object . __len__ ( self ) -> int
# Number of key-value pairs.
object . __contains__ ( self , key ) -> bool
# Called by 'key in d'. Default falls back to __iter__.
4.2 collections.abc.Mapping Abstract Base # Inheriting from Mapping requires only: __getitem__, __iter__, __len__
# Provides default implementations of: get, __contains__, keys, items, values, __eq__, __ne__
# Inheriting from MutableMapping additionally requires: __setitem__, __delitem__
# Provides: pop, popitem, clear, update, setdefault
4.3 Merge Protocol Dunders object . __or__ ( self , other ) -> dict # d1 | d2 (Python 3.9+)
object . __ior__ ( self , other ) -> self # d1 |= d2
object . __ror__ ( self , other ) -> dict # other | d1 (reflected)
4.4 __missing__ Protocol object . __missing__ ( self , key ) -> value
# Called by dict.__getitem__ if key is not found.
# Used by collections.defaultdict.
5. Behavioral Specification 5.1 dict() Constructor dict() → empty dict. dict(**kwargs) → dict from keyword arguments. dict(mapping) → dict from any mapping. dict(iterable) → dict from iterable of (key, value) pairs. dict(mapping, **kwargs) → combines mapping and keyword args; kwargs override. 5.2 dict Methods (Full Spec) Method Description Returns d[key] Get value; KeyError if absent value d[key] = val Set/update value — del d[key] Delete key; KeyError if absent — key in d Membership test (O(1)) bool key not in d Non-membership test bool d.get(key[, default]) Get value or default (no exception) value or default d.setdefault(key[, default]) Get value; insert default if missing value d.pop(key[, default]) Remove and return; KeyError if no default value d.popitem() Remove and return last inserted (LIFO); KeyError if empty (key, value) d.update([other[, **kwargs]]) Update from mapping/iterable/kwargs None d.clear() Remove all items None d.copy() Shallow copy dict d.keys() View of keys dict_keys d.values() View of values dict_values d.items() View of (key, value) pairs dict_items
5.3 View Objects (dict_keys, dict_values, dict_items) Views are dynamic : they reflect changes to the dict. dict_keys and dict_items support set operations (|, &, -, ^) if values are hashable. dict_keys supports in test in O(1). Views can be iterated but not indexed. Mutating the dict during iteration of a view raises RuntimeError. 5.4 setdefault vs get vs d[key] # d.get(key, default): read-only; does not modify dict
val = d . get ( "x" , 0 )
# d.setdefault(key, default): inserts if missing; modifies dict
val = d . setdefault ( "x" , 0 )
# d[key]: raises KeyError if missing
val = d [ "x" ]
5.5 defaultdict (collections.defaultdict) Subclass of dict; defines __missing__ to call the default_factory. defaultdict(list) — missing key returns [] and inserts it. defaultdict(int) — missing key returns 0. default_factory is called with no arguments. 5.6 ** Unpacking in Dict Literals {**d1, **d2, "extra": val} creates a new dict. For duplicate keys, the last occurrence wins. Available since Python 3.5 (PEP 448). 6. Defined vs Undefined Behavior 6.1 Defined d[key] raises KeyError for absent keys. del d[key] raises KeyError for absent keys. Insertion order preserved since Python 3.7 (spec guarantee). d.popitem() removes the last inserted item (LIFO since Python 3.7). True and 1 are the same key; False and 0 are the same key. Merging with | or update: last-writer-wins for duplicate keys. Views reflect the current state of the dict dynamically. 6.2 Undefined / Implementation-Defined Exact id() of dict internals: implementation-defined. Hash collision strategy: CPython uses open addressing with pseudo-random probing. The exact probing sequence is not specified. Dict resizing threshold: CPython resizes when load factor exceeds 2/3. The growth factor and exact table sizes are implementation details. d.keys() vs d.values() consistency under concurrent modification: not defined for multithreaded use without the GIL. 7. Edge Cases from the Spec (CPython-Specific Notes) 7.1 True/1/1.0 Are the Same Key d = {}
d [ 1 ] = "one"
d [ True ] = "true" # overwrites d[1]!
d [ 1.0 ] = "float one" # overwrites d[True]!
print ( d ) # {1: 'float one'}
print ( len ( d )) # 1
print ( d [ True ]) # 'float one'
print ( d [ 1.0 ]) # 'float one'
7.2 Dict Comprehension Duplicate Keys # Last value wins for duplicate keys in comprehension:
d = { k % 3 : k for k in range ( 9 )}
print ( d ) # {0: 6, 1: 7, 2: 8} — last assignment wins
7.3 RuntimeError on Mutation During Iteration d = { "a" : 1 , "b" : 2 , "c" : 3 }
try :
for k in d :
del d [ k ] # RuntimeError!
except RuntimeError as e :
print ( e ) # dictionary changed size during iteration
# Safe: iterate over a copy
for k in list ( d . keys ()):
del d [ k ]
print ( d ) # {}
7.4 setdefault for Grouping data = [( "Alice" , 90 ), ( "Bob" , 85 ), ( "Alice" , 95 ), ( "Bob" , 88 )]
groups = {}
for name , score in data :
groups . setdefault ( name , []) . append ( score )
print ( groups )
# {'Alice': [90, 95], 'Bob': [85, 88]}
# Equivalent with defaultdict:
from collections import defaultdict
groups2 = defaultdict ( list )
for name , score in data :
groups2 [ name ] . append ( score )
print ( dict ( groups2 ))
7.5 popitem() LIFO Order (Python 3.7+) d = { "a" : 1 , "b" : 2 , "c" : 3 }
print ( d . popitem ()) # ('c', 3) — last inserted
print ( d . popitem ()) # ('b', 2)
print ( d . popitem ()) # ('a', 1)
7.6 Dict View Set Operations d1 = { "a" : 1 , "b" : 2 , "c" : 3 }
d2 = { "b" : 20 , "c" : 30 , "d" : 40 }
common_keys = d1 . keys () & d2 . keys ()
print ( common_keys ) # {'b', 'c'}
only_d1 = d1 . keys () - d2 . keys ()
print ( only_d1 ) # {'a'}
# items() supports set ops only if values are hashable:
common_items = d1 . items () & d2 . items ()
print ( common_items ) # set() — no item is identical in both
7.7 | Merge Operator (Python 3.9+) defaults = { "color" : "blue" , "size" : "medium" , "weight" : 1.0 }
overrides = { "color" : "red" , "material" : "steel" }
config = defaults | overrides
print ( config )
# {'color': 'red', 'size': 'medium', 'weight': 1.0, 'material': 'steel'}
# In-place:
defaults |= overrides
print ( defaults )
# Same result; defaults modified in-place
8. Version History (PEPs and Python Versions) Feature PEP Version dict built-in — Python 1.0 dict.keys(), values(), items() returning views (not lists) PEP 3106 Python 3.0 Dict comprehensions {k: v for ...} PEP 274 Python 2.7 / 3.0 ** unpacking in dict literals and calls PEP 448 Python 3.5 typing.TypedDict PEP 589 Python 3.8 Insertion-order preservation as language spec guarantee — Python 3.7 CPython 3.6 ordered dict (implementation detail) — Python 3.6 dict | dict merge operator PEP 584 Python 3.9 dict |= dict in-place merge PEP 584 Python 3.9 dict[K, V] subscript in type hints PEP 585 Python 3.9 typing.TypedDict with Required/NotRequired PEP 655 Python 3.11
9. Implementation-Specific Behavior 9.1 CPython dict Compact Representation Since CPython 3.6, dict uses a compact representation: a sparse index array + a dense array of entries. This reduces memory usage compared to the pre-3.6 open-addressing table. Insertion order is maintained by the dense entries array. 9.2 CPython dict Memory Empty dict: sys.getsizeof({}) = 64 bytes (CPython 3.12 on 64-bit). Each entry: ~50 bytes (key pointer + value pointer + hash). 9.3 CPython Resizing dict resizes (doubles) when more than 2/3 of the index array is used. Resize triggers rehashing of all entries. Deletion does not immediately shrink the dict; only resize does. 9.4 PyPy PyPy has a similar ordered dict implementation. May differ in memory usage and exact resize thresholds. sys.getsizeof() results differ from CPython. 9.5 collections.OrderedDict vs dict In Python 3.7+, dict preserves insertion order, making OrderedDict redundant for most uses. OrderedDict still has move_to_end(key, last=True) and supports comparison that considers order. Two OrderedDicts with same items in different order are NOT equal; two dicts are. 10. Spec Compliance Checklist 11. Official Examples (Runnable Python 3.10+) from collections import defaultdict , Counter , ChainMap , OrderedDict
from typing import TypedDict
# ----------------------------------------------------------------
# 1. Creating dicts
# ----------------------------------------------------------------
d1 = { "name" : "Alice" , "age" : 30 }
d2 = dict ( name = "Bob" , age = 25 )
d3 = dict ([( "x" , 1 ), ( "y" , 2 )])
d4 = { k : k ** 2 for k in range ( 5 )}
print ( d1 ) # {'name': 'Alice', 'age': 30}
print ( d4 ) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# ----------------------------------------------------------------
# 2. Access patterns
# ----------------------------------------------------------------
d = { "a" : 1 , "b" : 2 }
print ( d [ "a" ]) # 1
print ( d . get ( "c" )) # None
print ( d . get ( "c" , 0 )) # 0
print ( d . setdefault ( "c" , 0 )) # inserts "c": 0, returns 0
print ( d ) # {'a': 1, 'b': 2, 'c': 0}
# ----------------------------------------------------------------
# 3. Mutation
# ----------------------------------------------------------------
d = { "a" : 1 }
d [ "b" ] = 2 # add
d [ "a" ] = 10 # update
del d [ "b" ] # delete
print ( d ) # {'a': 10}
d . update ({ "b" : 20 , "c" : 30 })
print ( d ) # {'a': 10, 'b': 20, 'c': 30}
val = d . pop ( "b" )
print ( val , d ) # 20 {'a': 10, 'c': 30}
last = d . popitem () # LIFO: ('c', 30)
print ( last , d ) # ('c', 30) {'a': 10}
# ----------------------------------------------------------------
# 4. dict views
# ----------------------------------------------------------------
d = { "x" : 1 , "y" : 2 , "z" : 3 }
keys = d . keys ()
values = d . values ()
items = d . items ()
d [ "w" ] = 4 # views update dynamically
print ( list ( keys )) # ['x', 'y', 'z', 'w']
print ( list ( values )) # [1, 2, 3, 4]
for k , v in items :
print ( f " { k } = { v } " , end = " " ) # x=1 y=2 z=3 w=4
print ()
# ----------------------------------------------------------------
# 5. Key membership
# ----------------------------------------------------------------
d = { "a" : 1 , "b" : 2 }
print ( "a" in d ) # True (O(1))
print ( "c" not in d ) # True
print ( "a" in d . keys ()) # True (equivalent)
print ( 1 in d . values ()) # True (O(n))
# ----------------------------------------------------------------
# 6. ** unpacking (PEP 448)
# ----------------------------------------------------------------
defaults = { "debug" : False , "timeout" : 30 }
overrides = { "timeout" : 60 , "verbose" : True }
config = { ** defaults , ** overrides }
print ( config ) # {'debug': False, 'timeout': 60, 'verbose': True}
# ----------------------------------------------------------------
# 7. | merge operator (Python 3.9+, PEP 584)
# ----------------------------------------------------------------
base = { "a" : 1 , "b" : 2 }
patch = { "b" : 20 , "c" : 30 }
merged = base | patch
print ( merged ) # {'a': 1, 'b': 20, 'c': 30}
base |= patch # in-place
print ( base ) # {'a': 1, 'b': 20, 'c': 30}
# ----------------------------------------------------------------
# 8. defaultdict
# ----------------------------------------------------------------
word_count = defaultdict ( int )
for word in "the quick brown fox jumps over the lazy dog" . split ():
word_count [ word ] += 1
print ( dict ( word_count ))
grouped = defaultdict ( list )
data = [( "math" , 90 ), ( "eng" , 85 ), ( "math" , 92 ), ( "eng" , 88 )]
for subject , score in data :
grouped [ subject ] . append ( score )
print ( dict ( grouped )) # {'math': [90, 92], 'eng': [85, 88]}
# ----------------------------------------------------------------
# 9. Counter
# ----------------------------------------------------------------
from collections import Counter
text = "mississippi"
c = Counter ( text )
print ( c ) # Counter({'s': 4, 'i': 4, 'p': 2, 'm': 1})
print ( c . most_common ( 2 )) # [('s', 4), ('i', 4)]
print ( c [ "s" ]) # 4
print ( c [ "z" ]) # 0 (no KeyError for Counter!)
# ----------------------------------------------------------------
# 10. True/1/1.0 same key
# ----------------------------------------------------------------
d = { 1 : "one" }
d [ True ] = "True" # overwrites d[1]
d [ 1.0 ] = "1.0" # overwrites again
print ( d ) # {1: '1.0'}
print ( len ( d )) # 1
# ----------------------------------------------------------------
# 11. TypedDict (Python 3.8+)
# ----------------------------------------------------------------
class Movie ( TypedDict ):
title : str
year : int
rating : float
# TypedDict is for static type checking only; runtime is just a dict:
m : Movie = { "title" : "Inception" , "year" : 2010 , "rating" : 8.8 }
print ( m [ "title" ]) # Inception
print ( isinstance ( m , dict )) # True
# ----------------------------------------------------------------
# 12. ChainMap (lookup through multiple dicts)
# ----------------------------------------------------------------
defaults = { "color" : "blue" , "user" : "guest" }
environ = { "user" : "alice" }
config = ChainMap ( environ , defaults )
print ( config [ "user" ]) # alice (environ wins)
print ( config [ "color" ]) # blue (from defaults)
print ( list ( config . keys ())) # ['user', 'color']
Section Topic URL §3.2 Mapping types https://docs.python.org/3/reference/datamodel.html#mapping-types §6.2.6 Dictionary displays https://docs.python.org/3/reference/expressions.html#dictionary-displays §3.3.7 __missing__ https://docs.python.org/3/reference/datamodel.html#object.missing dict Built-in dict type https://docs.python.org/3/library/stdtypes.html#mapping-types-dict collections OrderedDict, defaultdict https://docs.python.org/3/library/collections.html collections.abc MutableMapping ABC https://docs.python.org/3/library/collections.abc.html typing.TypedDict Typed dict https://docs.python.org/3/library/typing.html#typing.TypedDict PEP 274 Dict comprehensions https://peps.python.org/pep-0274/ PEP 448 Unpacking generalizations https://peps.python.org/pep-0448/ PEP 584 Dict merge operators \| https://peps.python.org/pep-0584/ PEP 585 dict[K, V] subscript https://peps.python.org/pep-0585/ PEP 589 TypedDict https://peps.python.org/pep-0589/ PEP 655 Required/NotRequired in TypedDict https://peps.python.org/pep-0655/ PEP 3106 Views for dict methods https://peps.python.org/pep-3106/
In this topic