multidict
raw JSON → 6.7.1 verified Tue May 12 auth: no python install: verified quickstart: stale
multidict is a mapping implementation that allows multiple values per key, mirroring the semantics of HTTP headers and query strings. It provides four concrete classes — MultiDict (mutable), MultiDictProxy (immutable view), CIMultiDict (case-insensitive mutable), and CIMultiDictProxy (case-insensitive immutable view) — plus the istr helper for pre-folded string keys. Current version is 6.7.1 (released January 2026). The library ships an optional C extension; release cadence is frequent patch releases with periodic minor releases adding features such as the new merge() method (6.6.0) and a hash-table backend replacing the previous O(N) array (6.5.0).
pip install multidict Common errors
error ModuleNotFoundError: No module named 'multidict' ↓
cause The `multidict` package is not installed in the Python environment being used, or there's a mismatch in virtual environments.
fix
Install the package using pip:
pip install multidict or python -m pip install multidict. error ERROR: Could not build wheels for multidict, which is required to install pyproject.toml-based projects ↓
cause This error occurs when `pip` cannot compile the optional C extension for `multidict` during installation. This often happens on systems without a C compiler, required Python development headers, or with newer Python versions (e.g., Python 3.12) for which pre-built wheels might not yet be available.
fix
Ensure you have a C compiler (e.g., Build Tools for Visual Studio on Windows, or
build-essential on Debian/Ubuntu) and Python development headers installed. Alternatively, you can force the installation of the pure-Python version (which will be slower) using the environment variable: MULTIDICT_NO_EXTENSIONS=1 pip install multidict. error KeyError: 'some_key' ↓
cause Attempting to access a key in a `MultiDict` (or its variants) using bracket notation `d['key']` when that key does not exist in the dictionary.
fix
Use the
.get() method, which returns None or a specified default value if the key is not found, or check for key existence with if 'key' in d: before accessing. To get all values for a key, use .getall('key'). error AttributeError: 'MultiDict' object has no attribute 'iteritems' ↓
cause This error typically occurs when migrating Python 2 code to Python 3. The `iteritems()`, `iterkeys()`, and `itervalues()` methods were removed in Python 3; `MultiDict` (like standard Python dictionaries) provides `items()`, `keys()`, and `values()` which return view objects.
fix
Replace
d.iteritems() with d.items(), d.iterkeys() with d.keys(), and d.itervalues() with d.values(). These methods return view objects in Python 3, which behave similarly to iterators for looping. error TypeError: can't pickle multidict._multidict.CIMultiDictProxy objects ↓
cause `CIMultiDictProxy` (and `MultiDictProxy`) are designed as immutable views (proxies) of underlying mutable multidicts and are explicitly not pickleable by design, mirroring Python's `types.MappingProxyType` behavior.
fix
If you need to serialize the data, convert the proxy object to a mutable
CIMultiDict or a standard dictionary first. For example, import pickle; dct = multidict.CIMultiDict(original_proxy); pickled_dct = pickle.dumps(dct) or pickled_dict = pickle.dumps(dict(original_proxy)). Warnings
gotcha d['key'] returns only the FIRST value for a duplicated key. Iterating .keys() yields ALL keys including duplicates. Use getall('key') to fetch every value. ↓
fix Replace d['key'] with d.getall('key') when multiple values per key are expected.
gotcha d['key'] = value replaces ALL existing entries for that key with a single entry, not just the first. Use d.add('key', value) to append without clobbering existing values. ↓
fix Use d.add(key, value) to append a value while preserving existing entries for the same key.
breaking 6.5.0 switched the internal storage from a linear array (O(N) lookups) to a hash table (O(1) lookups). MultiDict.add() and extend() are now 25-50% slower; single lookups are 25-50% faster and bulk operations are 2-3x faster. Code relying on iteration order of keys() after deletions may observe subtly different compaction behaviour. ↓
fix Upgrade to >=6.5.1 which fixes a resize-with-deleted-slots bug introduced in 6.5.0. Treat key iteration order as insertion-order stable but do not assume slot density.
breaking MultiDictProxy and CIMultiDictProxy raise TypeError if passed a plain dict or any non-MultiDict mapping. They only accept the matching MultiDict subclass. ↓
fix Wrap the source mapping in MultiDict() first: MultiDictProxy(MultiDict(plain_dict)).
gotcha Setting MULTIDICT_NO_EXTENSIONS=1 at runtime forces the pure-Python implementation even when the C extension wheel is installed. The pure-Python path is roughly 20-50x slower. This env var also suppresses C extension compilation at build time if set during pip install. ↓
fix Do not set MULTIDICT_NO_EXTENSIONS in production. For Alpine Linux / source builds without a C compiler, the pure-Python wheel is automatically used as a fallback starting with 6.x.
deprecated Directly importing from private submodules multidict._multidict (C extension) or multidict._multidict_py (pure Python) is not part of the public API and can break across patch releases. The internal _Impl class was removed from the pure-Python implementation in 6.4.x. ↓
fix Always import from the top-level package: from multidict import MultiDict, CIMultiDict, ...
gotcha CIMultiDict stores and returns keys as istr (a str subclass with preserved original case), not plain str. Identity or strict type checks against str will pass since istr derives from str, but isinstance checks for exactly str may differ from expectations when iterating keys. ↓
fix Use str(key) if you need a plain str, or compare with == rather than 'is'. istr is a subclass of str so equality checks work normally.
gotcha `MultiDict` performs case-sensitive key lookups, while `CIMultiDict` performs case-insensitive lookups. If you are expecting case-insensitive behavior (e.g., when working with HTTP headers), ensure you are using `CIMultiDict`. ↓
fix For case-insensitive key access, use `CIMultiDict` instead of `MultiDict`. Ensure the object `ci` is an instance of `CIMultiDict` if you expect `assert 'content-type' in ci` to pass when only 'Content-Type' is present.
Install
MULTIDICT_NO_EXTENSIONS=1 pip install multidict Install compatibility verified last tested: 2026-05-12
python os / libc status wheel install import disk
3.10 alpine (musl) - - 0.03s 18.8M
3.10 slim (glibc) - - 0.02s 19M
3.11 alpine (musl) - - 0.03s 20.3M
3.11 slim (glibc) - - 0.03s 21M
3.12 alpine (musl) - - 0.02s 12.2M
3.12 slim (glibc) - - 0.02s 13M
3.13 alpine (musl) - - 0.02s 11.8M
3.13 slim (glibc) - - 0.01s 13M
3.9 alpine (musl) - - 0.02s 18.2M
3.9 slim (glibc) - - 0.02s 19M
Imports
- MultiDict wrong
from multidict._multidict import MultiDictcorrectfrom multidict import MultiDict - CIMultiDict wrong
from multidict._multidict_py import CIMultiDictcorrectfrom multidict import CIMultiDict - MultiDictProxy
from multidict import MultiDictProxy - CIMultiDictProxy
from multidict import CIMultiDictProxy - istr
from multidict import istr - getall wrong
d['key']correctd.getall('key')
Quickstart stale last tested: 2026-04-23
from multidict import MultiDict, MultiDictProxy, CIMultiDict, istr
# --- Mutable multi-valued dict ---
d = MultiDict([('a', 1), ('b', 2), ('a', 3)])
assert d['a'] == 1 # returns FIRST value only
assert d.getall('a') == [1, 3] # all values for key
d.add('b', 99) # append without replacing
assert len(d) == 4
# --- set() replaces ALL entries for that key ---
d['a'] = 99
assert d.getall('a') == [99]
# --- Read-only proxy (dynamic view) ---
proxy = MultiDictProxy(d)
assert proxy['b'] == 2
d.add('b', 42)
assert proxy.getall('b') == [2, 99, 42] # proxy reflects changes
# --- Case-insensitive dict ---
ci = CIMultiDict(Content_Type='application/json')
assert 'content-type' in ci
assert ci['CONTENT-TYPE'] == 'application/json'
# --- istr for pre-folded keys (performance) ---
HDR = istr('Content-Type')
assert ci[HDR] == 'application/json'
# --- merge (6.6+): copies key only if not already present ---
base = MultiDict(a=1)
base.merge(MultiDict(a=99, b=2))
assert base['a'] == 1 # not overwritten
assert base['b'] == 2