Как проверить, что ключ словаря уникальный и не происходит перезапись значений?
Что проверяют
Понимаете ли вы, что словарь в Python перезаписывает значение молча, без ошибки и предупреждения. Интервьюер хочет увидеть несколько способов защиты и понимание того, что считается «тем же ключом». Следом часто спрашивают про словарь из списка пар или из JSON с повторами.
Как отвечать
Способы в зависимости от задачи:
- явная проверка:
if key in d: raise KeyError(key)перед записью. Проверка идёт по хэшу, в среднем за O(1); d.setdefault(key, value): записывает, только если ключа ещё нет, и возвращает значение, которое в итоге лежит в словаре. Удобно для сценария «записать, если пусто» без отдельной проверки;- свой класс: наследник
collections.UserDictс переопределённым__setitem__, который бросает исключение на повтор. Наследоваться отdictздесь хуже: егоupdate()и конструктор не вызывают ваш__setitem__, и защиту легко обойти; - проверка готовых данных: при
dict(pairs)дубли теряются, поэтому сравнивайтеlen(d)сlen(pairs), а сами повторы находите черезcollections.Counter; - JSON:
json.loadsпри повторе ключа оставляет последнее значение. Параметрobject_pairs_hookполучает все пары и позволяет бросить ошибку на дубле.
Отдельно проговорите, что ключи совпадают по хэшу и ==, а не по виду. 1, 1.0 и True это один и тот же ключ: {1: 'a', True: 'b'} превращается в {1: 'b'}. Для собственных классов нужны согласованные __eq__ и __hash__, а поля, участвующие в хэше, не должны меняться после вставки.
При слиянии действует правило «побеждает правый»: d1 | d2 (Python 3.9+) и {**d1, **d2} берут значение из второго словаря, поэтому пересечение ключей стоит проверить заранее через d1.keys() & d2.keys().
Где спотыкаются
Считают, что повторный ключ вызовет ошибку. Даже литерал {'a': 1, 'a': 2} выполняется без ошибок, дубль замечает только линтер вроде ruff.
Наследуются от dict, переопределяют __setitem__ и не замечают, что update() пишет в обход.
Забывают про многопоточность: пара «проверил, потом записал» не атомарна, между шагами другой поток может записать тот же ключ. setdefault в CPython для обычных ключей делает это одной операцией.