TypeError: unhashable type: 'slice' (tested fix)

TypeError: unhashable type: 'slice' means code like d[0:2] tried to slice a dict. Since Python 3.12 slices are hashable, so the same bug now shows up as KeyError: slice(0, 2, None). We reproduced both on 3.11 and 3.13 and tested the fixes.

1. Cause: slicing a dict

import sys
print(sys.version.split()[0])
d = {"a": 1, "b": 2, "c": 3}
try:
    d[0:2]                     # slicing a dict
except (TypeError, KeyError) as e:
    print(f"{type(e).__name__}: {e}")
try:
    hash(slice(0, 2))
    print("slice is hashable")
except TypeError as e:
    print(f"{type(e).__name__}: {e}")

Output on Python 3.11:

3.11.17
TypeError: unhashable type: 'slice'
TypeError: unhashable type: 'slice'

Output on Python 3.13:

3.13.5
KeyError: slice(0, 2, None)
slice is hashable

d[0:2] passes a slice(0, 2, None) object to the dict as a key. Dicts have no order-based slicing, so Python hashes the slice to look it up. Up to Python 3.11 slices were unhashable, giving TypeError: unhashable type: 'slice'. Python 3.12 made slice objects hashable, so the same line now raises KeyError: slice(0, 2, None). Same bug, different message after an upgrade.

2. Fix: slice the items, then rebuild the dict

# Fix: slice the keys/items, not the dict
from itertools import islice
d = {"a": 1, "b": 2, "c": 3}
print(dict(list(d.items())[0:2]))
print(dict(islice(d.items(), 2)))     # no full list copy

Output (Python 3.13):

{'a': 1, 'b': 2}
{'a': 1, 'b': 2}

Dicts keep insertion order, so "the first two entries" is well defined. itertools.islice avoids copying every item into a list first, which matters for large dicts.

3. A custom __getitem__ that forwards to a dict

# A custom class whose __getitem__ uses the key in a dict
class Cache:
    def __init__(self): self.data = {0: "x", 1: "y", 2: "z"}
    def __getitem__(self, key): return self.data[key]
c = Cache()
try:
    c[0:2]
except (TypeError, KeyError) as e:
    print(f"{type(e).__name__}: {e}")
class Fixed(Cache):
    def __getitem__(self, key):
        if isinstance(key, slice):
            return [self.data[i] for i in range(*key.indices(len(self.data)))]
        return self.data[key]
print(Fixed()[0:2])

Output on Python 3.11:

TypeError: unhashable type: 'slice'
['x', 'y']

Output on Python 3.13:

KeyError: slice(0, 2, None)
['x', 'y']

Classes that wrap a dict (caches, datasets, config objects) receive the slice in __getitem__ and pass it straight to the dict. Check isinstance(key, slice) and expand it with key.indices(len(...)), which also handles negative and missing bounds.

Related: unhashable type: 'list' · unhashable type: 'dict' · slice indices must be integers or None.

Frequently asked questions

What causes unhashable type: 'slice'?

Slicing a dict (or a class that forwards the key to a dict), as in d[0:2]. On Python 3.11 and earlier, slices could not be hashed.

Why do I get KeyError: slice(0, 2, None)?

Same bug on Python 3.12+: slices became hashable, so the dict looks the slice up as a key and does not find it.

How do I get the first n items of a dict?

dict(itertools.islice(d.items(), n)), or dict(list(d.items())[:n]).

Written with AI assistance. Measured on 2026-10-23 with Python 3.13.5 on Debian and 3.11.17 / 3.8.20 in python:alpine containers: d[0:2] on a dict, hash(slice), two fixes, and a dict-backed __getitem__. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.