Как merge(*dicts, **extra) объединяет словари в Python

Как merge(*dicts, **extra) объединяет словари в Python

@python_quiz

Разберем этот квиз

Коротко: функция берет произвольное количество позиционных словарей и именованных аргументов, обновляет один результирующий словарь последовательно и печатает его. Важно — последняя запись перезаписывает предыдущие значения по ключу.

Исходный код и поведение

python

Что делает код:

  • *dicts собирает все позиционные аргументы (в данном случае два словаря) в кортеж.
  • **extra собирает именованные аргументы в словарь extra.
  • Для каждого словаря из dicts вызывается res.update(d) (здесь это сделано внутри list comprehension, но результат списка игнорируется).
  • Затем res.update(extra) — обновляет res ключами из именованных аргументов.
  • В конце печатается res.

Результат выполнения:

Почему значение 'a' равно 3: ключ 'a' сначала был в позиционном словаре со значением 1, но затем был перезаписан именованным аргументом a=3, потому что update(extra) выполняется позже.

Анализ вариантов (кратко)

  • Вариант с {'a': 1, 'b': 2, 'c': 4} предполагает, что значение из именованного аргумента не перезаписывает существующее — неверно, потому что update выполняется после добавления позиционных словарей.
  • Вариант с {'a': 3, 'b': 2, 'c': 4} соответствует реальному поведению функции — именованный аргумент перезаписал 'a'.
  • Вариант с дублирующимся ключом в выводе {'a': 1, 'b': 2, 'a': 3, 'c': 4} невозможно: в словаре не может быть два одинаковых ключа.
  • Вариант с ошибкой о двойной передаче ключа неверен в этом случае: передача одинакового ключа в позиционном словаре и через именованный аргумент не вызывает исключения внутри такого сигнатуры функции (*dicts, **extra).

Нюансы и рекомендации

  1. List comprehension для сайд-эффектов

- Строка [res.update(d) for d in dicts] создаёт ненужный список из None и считается плохой практикой.
- Лучше использовать обычный цикл:
`python
for d in dicts:
res.update(d)
`

  1. Порядок обновления имеет значение

- Позиционные словари обновляются первыми, затем именованные аргументы. Последние записи побеждают при совпадении ключей.

  1. Если нужно обнаруживать конфликты

- Если вы хотите запретить перезапись ключей и поднять ошибку при конфликте, можно проверить пересечения ключей:
`python
def merge_strict(*dicts, **extra):
res = {}
for d in dicts:
overlap = set(res) & set(d)
if overlap:
raise ValueError(f"Duplicate keys in positional dicts: {overlap}")
res.update(d)
overlap_extra = set(res) & set(extra)
if overlap_extra:
raise ValueError(f"Duplicate keys between positional dicts and kwargs: {overlap_extra}")
res.update(extra)
return res

print(merge_strict({'a':1}, {'b':2}, a=3, c=4)) # вызовет ошибку из-за 'a'
`

  1. Возврат вместо печати

- Лучше, чтобы функция возвращала словарь, а не печатала его:
`python
def merge_return(*dicts, **extra):
res = {}
for d in dicts:
res.update(d)
res.update(extra)
return res

result = merge_return({'a':1}, {'b':2}, a=3, c=4)
print(result)
`

  1. Варианты передачи аргументов

- Если вы сделаете распаковку словаря в вызове, например merge(**{'a':1}), то этот словарь попадёт в именованные аргументы (extra), а не в positional dicts.
- Если цель — объединить несколько словарей и приоритет задать в определённом порядке, контролируйте порядок вызова update.

Вывод

Функция ведёт себя предсказуемо: обновления выполняются последовательно, и последние записи по ключу заменяют предыдущие. Использование list comprehension ради побочного эффекта нежелательно; предпочтительнее чистые циклы и возвращение результата вместо принта. Если перезапись ключей недопустима, добавьте проверку конфликтов.

Report Page